com-lihaoyi / mill

Mill is a fast JVM build tool that supports Java and Scala. 2-4x faster than Gradle and 5-10x faster than Maven for common workflows, Mill aims to make your project’s build process performant, maintainable, and flexible
https://mill-build.org/
MIT License
2.18k stars 346 forks source link

Incorrect ordering of require statements for tests #844

Open xfactor2000 opened 4 years ago

xfactor2000 commented 4 years ago

Hi, I use Mill for compiling Scala.js frontend application. I noticed that for test task, the JS requirements are ordered incorrectly, while for fastOpt and fullOpt everything seems to work fine.

In my code, I have written a Leaflet.js facade:

@JSImport("leaflet", Namespace)
@js.native
object L extends LPolylineDecorator {
  def marker(coords: js.Tuple2[String, String], options: js.Dynamic): js.Dynamic = js.native

  def popup(options:js.Dynamic):js.Dynamic = js.native

  @js.native
  class TileLayer(url: String, attribution: js.Any) extends js.Object

  @js.native
  class LatLng(override val centerLat: String, override val centerLon: String) extends LatLngLike

  def layerGroup(): LayerGroup = js.native

  def icon(settings: js.Dynamic): js.Object = js.native

  @js.native
  trait LayerGroup extends js.Object {
    def clearLayers():Unit = js.native
    def addLayer(marker: js.Dynamic):Unit = js.native
  }

  @js.native
  class Map(mapDivId: String) extends MapLike {
    def setView(latLng: LatLngLike, zoom: Double): Unit = js.native
    def addLayer(layer: js.Any): Unit = js.native
    def on(eventName: String, handler: js.Function): Unit = js.native
    def getZoom(): Double = js.native
    def getCenter(): MapCenter = js.native
    def addControl(control: js.Any): Unit = js.native
    def removeLayer(layer:js.Any): Unit = js.native

    def eachLayer(handler: js.Function1[js.Object,Unit]): Unit = js.native
  }

  @js.native
  def polyline(coords: js.Array[js.Tuple2[String,String]], attribs: js.Object): js.Dynamic = js.native

  @js.native
  object Control extends js.Object{
    @js.native
    def extend(options: LControlExtendOptions): js.Dynamic  = js.native
  }

  @js.native
  object DomUtil extends js.Object{
    @js.native
    def create(controlType: String): js.Dynamic = js.native
  }

}

trait LatLngLike extends js.Object{
  val centerLat: String
  val centerLon: String
}

trait MapLike extends js.Object{
  def addControl(control: js.Any): Unit
  def setView(latLng: LatLngLike, zoom: Double): Unit
  def addLayer(layer: js.Any): Unit
  def on(eventName: String, handler: js.Function): Unit
  def getZoom(): Double
  def getCenter(): MapCenter
  def eachLayer(handler:js.Function1[js.Object,Unit]): Unit

  trait MapCenter extends js.Object {
    val lat: Double
    val lng: Double
  }

}

trait LControlExtendOptions extends js.Object{
  val options: js.Object
  val onAdd: js.Function
}

object LControlExtendOptions {
  def apply(options: js.Object, onAdd: js.Function): LControlExtendOptions = {
    js.Dynamic.literal(options = options, onAdd = onAdd).asInstanceOf[LControlExtendOptions]
  }
}

trait LGlyphExtendOptions extends js.Object{
  val options: js.Object
}

object LGlyphExtendOptions {
  def apply(options: js.Object): LGlyphExtendOptions = {
    js.Dynamic.literal(options = options).asInstanceOf[LGlyphExtendOptions]
  }
}

@JSImport("leaflet-polylinedecorator", Namespace)
@js.native
class LPolylineDecorator extends LGlyph {

  @js.native
  object Symbol extends js.Object{
    @js.native
    def arrowHead(options: js.Any): js.Any = js.native
  }

  @js.native
  def polylineDecorator(route: js.Any, options: js.Any): js.Dynamic = js.native
}

@JSImport("leaflet.icon.glyph", Namespace)
@js.native
class LGlyph extends LSidebar {

  @js.native
  object Icon extends js.Object{
    @js.native
    object Glyph extends js.Object {
      def extend(options: LGlyphExtendOptions): js.Dynamic  = js.native
    }
  }
}

@JSImport("leaflet-sidebar", Namespace)
@js.native
class LSidebar extends js.Object{
  @JSName("control")
  @js.native
  object control extends js.Object{
    @JSName("sidebar")
    @js.native
    def sidebar(placeHolder:String, options: js.Dynamic): Sidebar = js.native
  }

}

trait Sidebar extends js.Object {
  def hide(): Unit
  def show(): Unit
  def toggle(): Unit
  def setContent(content:String): Unit
  def getContainer():js.Dynamic
}

As you see, I'm using several additional Leaflet modules such as leaflet-sidebar. While running the tests, I got an exception:

Starting process: node

/home/xxx/xxx/node_modules/leaflet-sidebar/src/L.Control.Sidebar.js:1
L.Control.Sidebar = L.Control.extend({
^
/home/xxx/xxx/node_modules/leaflet-sidebar/src/L.Control.Sidebar.js:1
L.Control.Sidebar = L.Control.extend({
^

ReferenceError: L is not defined
    at Object.<anonymous> (/home/anton/work/navint-navinsights-server/node_modules/leaflet-sidebar/src/L.Control.Sidebar.js:1:1)
    at Module._compile (internal/modules/cjs/loader.js:1123:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1143:10)
    at Module.load (internal/modules/cjs/loader.js:972:32)
    at Function.Module._load (internal/modules/cjs/loader.js:872:14)
    at Module.require (internal/modules/cjs/loader.js:1012:19)
    at require (internal/modules/cjs/helpers.js:72:18)
    at /home/anton/work/navint-navinsights-server/out/frontend/test/fastOptTest/dest/out.js:760:30
    at Script.runInThisContext (vm.js:131:20)
    at Object.runInThisContext (vm.js:297:38)
1 targets failed
frontend.test.test org.scalajs.testing.common.RPCCore$ClosedException: org.scalajs.testing.adapter.JSEnvRPC$RunTerminatedException
org.scalajs.testing.adapter.JSEnvRPC$RunTerminatedException
org.scalajs.jsenv.ExternalJSRun$NonZeroExitException: exited with code 1

I dag into out.js in fastOptTest folder and found the problem:

var $i_leaflet$002dsidebar = require("leaflet-sidebar");
var $i_luxon = require("luxon");
var $i_jquery = require("jquery");
var $i_leaflet = require("leaflet");
var $i_leaflet$002eicon$002eglyph = require("leaflet.icon.glyph");
var $i_leaflet$002dpolylinedecorator = require("leaflet-polylinedecorator");

As you see, "leaflet" is imported after "leaflet-sidebar", which is incorrect. In fastOpt folder's out.js the code is generated correctly:

var $i_flatpickr = require("flatpickr");
var $i_leaflet = require("leaflet");
var $i_leaflet$002dsidebar = require("leaflet-sidebar");
var $i_luxon = require("luxon");
var $i_sweetalert2 = require("sweetalert2");
var $i_jquery = require("jquery");
var $i_leaflet$002eicon$002eglyph = require("leaflet.icon.glyph");
var $i_leaflet$002dpolylinedecorator = require("leaflet-polylinedecorator");

The same test code compiled and ran perfectly on SBT.

antonb-via commented 4 years ago

Correction - same things happens during fullOpt and fastOpt for regular modules as well:

While using jquery-ui plugin with this facade:

@JSImport("jquery", Namespace)
@js.native
object jQuery extends  jQueryUI {
  def ready(handler: js.Function): Unit = js.native
  def apply(handler: js.Any): this.type = js.native
  def remove(): Unit = js.native
  def keyup(handler: js.Function1[js.Dynamic,Unit]):Unit = js.native
  def keypress(handler: js.Function1[js.Dynamic,Unit]):Unit = js.native

  @JSName("val")
  def value(): String = js.native

  def html(value:String):Unit = js.native

  def text(): String = js.native
  def is(attr: String):Boolean = js.native

  @JSName("val")
  def value(value: String): js.Dynamic = js.native

  @JSName("val")
  def valueSet(value: String): Unit = js.native

  def show(): Unit = js.native
  def click(handler: js.ThisFunction): Unit = js.native

  @JSName("get")
  def getDomElement(index:Int): dom.html.Element = js.native

  def on(event: String, handler: js.ThisFunction): Unit = js.native
  def bind(event: String, handler: js.Function): Unit = js.native
  def append(element: js.Dynamic): Unit = js.native
  def append(element: js.Object): Unit = js.native
  def append(html: String): Unit = js.native
  def get(url:String, handler: js.Function1[js.Dynamic,Unit]): Unit = js.native

  def post(url:String, data: js.Object):Callback = js.native
  def prop(name:String, value: Boolean): Unit = js.native

  def prop(name:String): Boolean = js.native

}

@JSImport("jquery-ui", Namespace)
@js.native
class jQueryUI extends js.Object{
  def slider(options:js.Dynamic):Unit = js.native
}

@js.native
trait Callback extends js.Object {
  def done(handler:js.Function):Callback = js.native
  def fail(handler: js.Function1[js.Object, Unit]):Callback = js.native
}

This is the code that gets generated, you can see that jquery-ui is imported before jquery module.

var $i_flatpickr = require("flatpickr");
var $i_leaflet = require("leaflet");
var $i_leaflet$002dsidebar$002dv2 = require("leaflet-sidebar-v2");
var $i_luxon = require("luxon");
var $i_sweetalert2 = require("sweetalert2");
var $i_jquery$002dui = require("jquery-ui");
var $i_leaflet$002dsidebar = require("leaflet-sidebar");
var $i_jquery = require("jquery");
var $i_leaflet$002eicon$002eglyph = require("leaflet.icon.glyph");
var $i_leaflet$002dpolylinedecorator = require("leaflet-polylinedecorator");