Skip to content

Linking error: tuplez-full-light (url-dsl) conflicts with tuplez-full (Airstream) in multi-module Scala.js projects #29

Description

@nguyenyou

Problem

When using Waypoint 10.0.0-M6 together with Laminar 18.0.0-M4 in a multi-module Scala.js project, the Scala.js linker fails due to conflicting tuplez versions on the classpath.

Root cause

Airstream 18.0.0-M4 depends on tuplez-full 0.5.0-M2, while Waypoint's transitive dependency url-dsl 0.7.0 depends on tuplez-full-light 0.4.0. These are different artifacts (different artifact IDs), so neither evicts the other during dependency resolution. Both end up on the classpath, each providing their own app.tulz.tuplez.Composition class with incompatible methods:

Airstream 18.0.0-M4 ──→ tuplez-full 0.5.0-M2       (has Composition.Scalar$plusT, no decompose)
Waypoint 10.0.0-M6  ──→ url-dsl 0.7.0 ──→ tuplez-full-light 0.4.0  (has Composition.decompose, no Scalar$plusT)

This means excluding either artifact breaks the other:

  • With both on classpath → Scalar$plusT not found (Airstream's combineWith fails)
  • Exclude tuplez-full-light → decompose not found (url-dsl's PathSegment./ fails)
  • Exclude tuplez-full → Scalar$plusT not found (same as having both)

Error messages

With both artifacts present (default):

Referring to non-existent method app.tulz.tuplez.Composition$.Scalar$plusT()app.tulz.tuplez.Composition
  called from constructor MyComponent.<init>(...)void

With tuplez-full-light excluded:

Referring to non-existent method app.tulz.tuplez.Composition.decompose(java.lang.Object)scala.Tuple2
  called from urldsl.language.PathSegment.$div(...)urldsl.language.PathSegment

Notes

  • Compile succeeds in all cases — the error only appears at Scala.js link time (both fastLinkJS and fullLinkJS) in multi-module setups where one module depends on Laminar and the downstream module adds Waypoint.
  • In a single-module project, the linker happens to pick the right class and the conflict doesn't surface.

Minimal reproduction

Requires Mill 1.1.2, Scala 3.8.2, Scala.js 1.20.2.

Project structure

├── build.mill
├── lib/src/MyComponent.scala    # depends on Laminar only
└── app/src/Main.scala           # depends on lib + Waypoint

build.mill

package build
import mill.*, scalalib.*, scalajslib.*, scalajslib.api.*

trait CommonJS extends ScalaJSModule {
  def scalaVersion   = "3.8.2"
  def scalaJSVersion = "1.20.2"
  def moduleKind     = ModuleKind.ESModule
}

object lib extends CommonJS {
  def mvnDeps = Seq(mvn"com.raquo::laminar::18.0.0-M4")
}

object app extends CommonJS {
  def mvnDeps    = Seq(mvn"com.raquo::waypoint::10.0.0-M6")
  def moduleDeps = Seq(lib)
}

lib/src/MyComponent.scala

package mylib
import com.raquo.laminar.api.L.*

class MyComponent {
  val combined = Var(1).signal.combineWith(
    Var(2).signal, Var(3).signal, Var(4).signal, Var(5).signal,
    Var(true).signal, Var(true).signal, Var(false).signal, Var(false).signal
  )
  def element: HtmlElement = div(child.text <-- combined.map(_.toString))
}

app/src/Main.scala

import mylib.MyComponent
object Main {
  def main(args: Array[String]): Unit = println(new MyComponent().element)
}

Reproduce

./mill app.compile    # ✓ succeeds
./mill app.fastLinkJS # ✗ linking error
./mill app.fullLinkJS # ✗ linking error

Suggested fix

Align the tuplez dependency across Waypoint's url-dsl and Airstream so they use the same artifact and version — ideally tuplez-full 0.5.0-M2 to match Airstream 18.0.0-M4.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions