What would be the correct way to add a dependency ...
# javascript
c
What would be the correct way to add a dependency if the user has configured a specific feature? For example, I need a specific dependency if the user has defined
useCommonJs()
. To detect that, I'm using:
Copy code
val moduleKind = project.objects.property<JsModuleKind>()
	project.tasks.withType<org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrLink> {
		moduleKind.set(compilerOptions.moduleKind)
	}
but by the time
moduleKind
is set, it's not allowed to add a
implementation(npm("foo", "1.0.0"))
anymore. I've also tried having a
: RequiresNpmDependencies
task with a dependency set that changes once
moduleKind
is known, and that works without the configuration cache, but with it I get a serialization crash within the configuration cache itself.
e
Are you referring to detecting what a consumer of your lib is using (CJS or ESM)?
Or is this from a Gradle plugin?
v
Copy code
implementation(moduleKind.map { npm("foo", "1.0.0") })
?
😜 1
c
@Edoardo Luppi Yes, this is a Gradle plugin, I want to detect what the user of the plugin has configured (so they don't have to configure it twice)
@Vampire I tried, but
implementation
is resolved before
moduleKind.set()
is called… If I could somehow do
implementation(project.tasks.withType<…>().flatMap { it.compilerOptions.moduleKind })
then that may work maybe, but I don't see such a function
e
kotlinx-benchmark does something like that IIRC. It creates a compilation and adds npm dependencies dynamically. Might be useful to go look at its sources.
a
@Adam Semenenko do you know how we do this inside KGP?
v
@Vampire I tried, but
implementation
is resolved before
moduleKind.set()
is called…
Maybe you should find out why. Whenever order matters, usually something somewhere is doing something wrong. In this case probably doing dependency resolution at configuration time.
If I could somehow do
implementation(project.tasks.withType<…>().flatMap { it.compilerOptions.moduleKind })
then that may work maybe, but I don't see such a function
That would probably not help either, because you have the same problem. You already evilly break task-configuration avoidance by using
tasks.withType(...) { ... }
which is the same as doing
tasks.withType(...).all { ... }
, and thus causes each and every task of that type to be configured, whether it will be executed or not and it does this immediately for all tasks that are already registered and for future ones as soon as they are registered. So if with that above snippet and using the property like I suggested the property is "set too late", then there is not much you can do besides becoming even dirtier like using
afterEvaluate { ... }
, which is not good of course. Maybe you should just not try to do that reaction? If you want to proved the user convenience, maybe you should instead provide some function in your plugin that the user invokes and that then does both tasks for him, the one you want to react to and the reaction? Or something like that.
c
> Whenever order matters, usually something somewhere is doing something wrong. If I had to guess what the sources of the problem are: •
kotlin.js().moduleKind
should be a
Provider
in the Kotlin extension instead of only being a provider available on a task •
: RequiresNpmDependencies
should be a
ListProperty
instead of a
Set
maybe you should instead provide some function in your plugin that the user invokes and that then does both tasks for him
Maybe, but I don't think that approach scales. What if there was a third plugin that had to work with both KGP and my plugin? Should it provide its own config, if my plugin is there configure it, and otherwise configure KGP directly? Conceptually, this is the configuration of KGP. My plugin just needs an additional dependency to be able to deal with it, but it doesn't otherwise care. I could also not provide anything automatically at all and document users that they need to add the dependencies x and y for this to work, but IMO the point of Gradle is that you can trust the defaults.
v
Maybe not "the point", but surely "one point". 😄
😅 1
âž• 1
How about
Copy code
implementation(
    js()
        .compilations
        .named("main")
        .flatMap { it.compileTaskProvider }
        .flatMap { it.compilerOptions.moduleKind }
        .filter { it == "MODULE_COMMONJS" }
        .map { npm("foo", "1.0.0") }
)
maybe?
That would also not cause task-configuration avoidance to break
And unless dependencies are resolved before the
useCommonJs()
call is made, this should hopefully work properly.
c
I tried to apply what you said, I wrote:
Copy code
val kotlinExt = project.kotlinExtension as KotlinMultiplatformExtension

	val moduleKind = kotlinExt.js().compilations.named("main")
		.flatMap { it.compileTaskProvider }
		.flatMap { it.compilerOptions.moduleKind }

	val plugins = moduleKind.map {
		when (it) {
			JsModuleKind.MODULE_COMMONJS, JsModuleKind.MODULE_UMD -> listOf(
				ExternalVitePlugin("viteCommonjs", "vite-plugin-commonjs", "0.10.4"),
				ExternalVitePlugin("commonjs", "@rollup/plugin-commonjs", "20.0.6"),
			)
			else -> emptyList()
		}
	}

	project.tasks.withType(WriteConfig::class.java).configureEach {
		config.plugins.addAll(plugins)

		doFirst {
			println("Plugins: ${plugins.get()}")
		}
	}
(this doesn't have the
implementation
part yet) but I get:
Copy code
A problem was found with the configuration of task ':viteConfigureProd' (type 'WriteConfig').
  - Type 'opensavvy.gradle.vite.base.tasks.WriteConfig' property 'plugins' doesn't have a configured value.
so I'm not sure it's going to work
v
If you leave out the
config.plugins.addAll
, does the
doFirst
print the expected plugins?
c
It does not.
Copy code
* What went wrong:
Execution failed for task ':app:viteConfigureProd'.
> Cannot query the value of this provider because it has no value available.
v
Well, then there is no module kind set, so you cannot
map
it. You probably need an
orElse(emptyList())
.
For dependencies it is different if KGP does it like the standard Gradle
addLater
where an absent provider is just ignored.
But there you add the
plugins
to
config.plugins
. I guess that is a
ListProperty
which becomes unset if you add an unset provider.
c
config.plugins
is a
ListProperty
, but that one I own and there is already a
.convention(emptyList())