From 2c7b6f9b97213754c442996c72ff03bc78d423f2 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Wed, 7 May 2014 10:41:02 +0200 Subject: [PATCH 01/16] Fix scala-js/scala-js#595: Test SBT plugin in published form on Travis --- .../src/main/scala/sbttest/withDOM/Lib.scala | 28 +++++++++++++++++++ .../main/scala/sbttest/withDOM/TestApp.scala | 14 ++++++++++ .../test/scala/sbttest/withDOM/LibTest.scala | 23 +++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/Lib.scala create mode 100644 sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/TestApp.scala create mode 100644 sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala diff --git a/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/Lib.scala b/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/Lib.scala new file mode 100644 index 0000000..e431557 --- /dev/null +++ b/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/Lib.scala @@ -0,0 +1,28 @@ +package sbttest.withDOM + +import scala.scalajs.js + +object Lib { + + val document: js.Dynamic = js.Dynamic.global.document + val jQuery: js.Dynamic = js.Dynamic.global.jQuery + + def getElementsByTagName(name: String): js.Array[js.Dynamic] = + document.getElementsByTagName(name).asInstanceOf[js.Array[js.Dynamic]] + + /** appends a

with the message to the document */ + def appendDocument(msg: String): Unit = { + val trg = { + val bodies = getElementsByTagName("body") + if (bodies.length > 0) + bodies(0) + else + document + } + + val elem = document.createElement("p") + elem.appendChild(document.createTextNode(msg)) + trg.appendChild(elem) + } + +} diff --git a/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/TestApp.scala b/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/TestApp.scala new file mode 100644 index 0000000..e61ed20 --- /dev/null +++ b/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/TestApp.scala @@ -0,0 +1,14 @@ +package sbttest.withDOM + +import scala.scalajs.js + +object TestApp extends js.JSApp { + + def main(): Unit = { + Lib.appendDocument("Hello World") + Lib.appendDocument("Still Here!") + + println(Lib.jQuery("p").text()) + } + +} diff --git a/sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala b/sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala new file mode 100644 index 0000000..f49e6b7 --- /dev/null +++ b/sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala @@ -0,0 +1,23 @@ +package sbttest.withDOM + +import scala.scalajs.js +import scala.scalajs.test.JasmineTest + +object LibTest extends JasmineTest { + + describe("Dummy Library") { + + it("should provide jQuery") { + expect(Lib.jQuery).toBeDefined + } + + it("should append an element") { + def count = Lib.jQuery("p").length.asInstanceOf[Int] + val oldCount = count + Lib.appendDocument("foo") + expect(count - oldCount).toEqual(1) + } + + } + +} From 1367e17739d6dd4873c0784174e491360a6fbaf8 Mon Sep 17 00:00:00 2001 From: Tobias Schlatter Date: Fri, 19 Sep 2014 14:09:52 +0200 Subject: [PATCH 02/16] Fix scala-js/scala-js#1060: Cleanup scala.scalajs.test namespace Its elements are moved to different namespaces as follows: Scala.js Test Suite => scala.scalajs.testsuite Test Bridge => scala.scalajs.testbridge Jasmine Test Framework => org.scalajs.jasminetest Also, the RhinoJSEnv is updated to undefine LiveConnect classes after including env.rhino.js. No research on why this helps has been performed (but it does help). --- .../withDOM/src/test/scala/sbttest/withDOM/LibTest.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala b/sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala index f49e6b7..e08e6e3 100644 --- a/sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala +++ b/sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala @@ -1,7 +1,8 @@ package sbttest.withDOM import scala.scalajs.js -import scala.scalajs.test.JasmineTest + +import org.scalajs.jasminetest.JasmineTest object LibTest extends JasmineTest { From 8e8aaadaa299a32396e28a31c9c4e56d9d3e3572 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 21 Dec 2015 16:34:09 +0100 Subject: [PATCH 03/16] Add DOMJSEnv based on 'jsdom' in Node.js. --- .../scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala new file mode 100644 index 0000000..3ba875f --- /dev/null +++ b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala @@ -0,0 +1,111 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + + +package org.scalajs.jsenv.nodejs + +import java.io.{Console => _, _} + +import org.scalajs.core.tools.io._ +import org.scalajs.core.tools.jsdep.ResolvedJSDependency +import org.scalajs.jsenv._ + +import org.scalajs.core.ir.Utils.escapeJS + +class JSDOMNodeJSEnv( + nodejsPath: String = "node", + addArgs: Seq[String] = Seq.empty, + addEnv: Map[String, String] = Map.empty +) extends AbstractNodeJSEnv(nodejsPath, addArgs, addEnv, sourceMap = false) { + + protected def vmName: String = "Node.js with JSDOM" + + override def jsRunner(libs: Seq[ResolvedJSDependency], + code: VirtualJSFile): JSRunner = { + new DOMNodeRunner(libs, code) + } + + override def asyncRunner(libs: Seq[ResolvedJSDependency], + code: VirtualJSFile): AsyncJSRunner = { + new AsyncDOMNodeRunner(libs, code) + } + + override def comRunner(libs: Seq[ResolvedJSDependency], + code: VirtualJSFile): ComJSRunner = { + new ComDOMNodeRunner(libs, code) + } + + protected class DOMNodeRunner(libs: Seq[ResolvedJSDependency], code: VirtualJSFile) + extends ExtRunner(libs, code) with AbstractDOMNodeRunner + + protected class AsyncDOMNodeRunner(libs: Seq[ResolvedJSDependency], code: VirtualJSFile) + extends AsyncExtRunner(libs, code) with AbstractDOMNodeRunner + + protected class ComDOMNodeRunner(libs: Seq[ResolvedJSDependency], code: VirtualJSFile) + extends AsyncDOMNodeRunner(libs, code) with NodeComJSRunner + + protected trait AbstractDOMNodeRunner extends AbstractNodeRunner { + + protected def codeWithJSDOMContext(): Seq[VirtualJSFile] = { + val scriptsJSPaths = getLibJSFiles().map { + case file: FileVirtualFile => file.path + case file => libCache.materialize(file).getAbsolutePath + } + val scriptsStringPath = scriptsJSPaths.map('"' + escapeJS(_) + '"') + val jsDOMCode = { + s""" + |(function () { + | const jsdom = require("jsdom"); + | var windowKeys = []; + | + | jsdom.env({ + | html: "", + | virtualConsole: jsdom.createVirtualConsole().sendTo(console), + | created: function (error, window) { + | if (error == null) { + | window["__ScalaJSEnv"] = __ScalaJSEnv; + | window["scalajsCom"] = global.scalajsCom; + | windowKeys = Object.keys(window); + | } else { + | console.log(error); + | } + | }, + | scripts: [${scriptsStringPath.mkString(", ")}], + | onload: function (window) { + | for (var k in window) { + | if (windowKeys.indexOf(k) == -1) + | global[k] = window[k]; + | } + | + | ${code.content} + | } + | }); + |})(); + |""".stripMargin + } + Seq(new MemVirtualJSFile("codeWithJSDOMContext.js").withContent(jsDOMCode)) + } + + override protected def getJSFiles(): Seq[VirtualJSFile] = + initFiles() ++ customInitFiles() ++ codeWithJSDOMContext() + + /** Libraries are loaded via scripts in Node.js */ + override protected def getLibJSFiles(): Seq[VirtualJSFile] = + libs.map(_.lib) + + // Send code to Stdin + override protected def sendVMStdin(out: OutputStream): Unit = { + /* Do not factor this method out into AbstractNodeRunner or when mixin in + * the traits it would use AbstractExtRunner.sendVMStdin due to + * linearization order. + */ + sendJS(getJSFiles(), out) + } + } + +} From 7ccf30d6660ffa2daefb16fedf5cb0bcd035bb1a Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 26 Apr 2016 11:22:01 +0200 Subject: [PATCH 04/16] Remove Jasmine from sbtPluginTest. --- .../test/scala/sbttest/withDOM/LibTest.scala | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala b/sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala index e08e6e3..d8b11d0 100644 --- a/sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala +++ b/sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala @@ -2,23 +2,18 @@ package sbttest.withDOM import scala.scalajs.js -import org.scalajs.jasminetest.JasmineTest - -object LibTest extends JasmineTest { - - describe("Dummy Library") { - - it("should provide jQuery") { - expect(Lib.jQuery).toBeDefined - } - - it("should append an element") { - def count = Lib.jQuery("p").length.asInstanceOf[Int] - val oldCount = count - Lib.appendDocument("foo") - expect(count - oldCount).toEqual(1) - } +import org.junit.Test +import org.junit.Assert._ +class LibTest { + @Test def dummy_library_should_provide_jQuery(): Unit = { + assertFalse(js.isUndefined(Lib.jQuery)) } + @Test def dummy_library_should_append_an_element(): Unit = { + def count = Lib.jQuery("p").length.asInstanceOf[Int] + val oldCount = count + Lib.appendDocument("foo") + assertEquals(1, count - oldCount) + } } From fdf02b9f66caa9eaf04cf560b9c18cd7476d06a8 Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Mon, 5 Dec 2016 09:42:40 +0100 Subject: [PATCH 05/16] Add history API support to JSDOMNodeJSEnv --- .../jsenv/test/JSDOMNodeJSEnvTest.scala | 23 +++++++++++++++++++ .../scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala | 1 + 2 files changed, 24 insertions(+) create mode 100644 js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/JSDOMNodeJSEnvTest.scala diff --git a/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/JSDOMNodeJSEnvTest.scala b/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/JSDOMNodeJSEnvTest.scala new file mode 100644 index 0000000..88d8e19 --- /dev/null +++ b/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/JSDOMNodeJSEnvTest.scala @@ -0,0 +1,23 @@ +package org.scalajs.jsenv.test + +import org.scalajs.jsenv.nodejs.JSDOMNodeJSEnv + +import org.junit.Test +import org.junit.Assert._ + +class JSDOMNodeJSEnvTest extends TimeoutComTests { + + protected def newJSEnv: JSDOMNodeJSEnv = new JSDOMNodeJSEnv() + + @Test + def historyAPI: Unit = { + """|console.log(window.location.href); + |window.history.pushState({}, "", "/foo"); + |console.log(window.location.href); + """.stripMargin hasOutput + """|http://localhost/ + |http://localhost/foo + |""".stripMargin + } + +} diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala index 3ba875f..cc0d4cc 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala @@ -77,6 +77,7 @@ class JSDOMNodeJSEnv( | }, | scripts: [${scriptsStringPath.mkString(", ")}], | onload: function (window) { + | jsdom.changeURL(window, "http://localhost"); | for (var k in window) { | if (windowKeys.indexOf(k) == -1) | global[k] = window[k]; From 8427547ab0e32922158f69eca3e8af7908babb4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Tue, 28 Mar 2017 16:51:41 +0200 Subject: [PATCH 06/16] Use VirtualJSFiles instead of ResolvedJSDependency in the JSEnv API. This removes the annoying dependency (sic!) of the `JSEnv` API on `ResolvedJSDependency`. This was the only part of `tools.jsdep` that was used in the `JSEnv` API, so it decouples those. This will be very important as we separate `jsDependencies` in a separate plugin, as the `tools.jsdep` package should go with that, out of the core. --- .../scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala index cc0d4cc..6027d50 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala @@ -12,7 +12,6 @@ package org.scalajs.jsenv.nodejs import java.io.{Console => _, _} import org.scalajs.core.tools.io._ -import org.scalajs.core.tools.jsdep.ResolvedJSDependency import org.scalajs.jsenv._ import org.scalajs.core.ir.Utils.escapeJS @@ -25,28 +24,28 @@ class JSDOMNodeJSEnv( protected def vmName: String = "Node.js with JSDOM" - override def jsRunner(libs: Seq[ResolvedJSDependency], + override def jsRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile): JSRunner = { new DOMNodeRunner(libs, code) } - override def asyncRunner(libs: Seq[ResolvedJSDependency], + override def asyncRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile): AsyncJSRunner = { new AsyncDOMNodeRunner(libs, code) } - override def comRunner(libs: Seq[ResolvedJSDependency], + override def comRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile): ComJSRunner = { new ComDOMNodeRunner(libs, code) } - protected class DOMNodeRunner(libs: Seq[ResolvedJSDependency], code: VirtualJSFile) + protected class DOMNodeRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile) extends ExtRunner(libs, code) with AbstractDOMNodeRunner - protected class AsyncDOMNodeRunner(libs: Seq[ResolvedJSDependency], code: VirtualJSFile) + protected class AsyncDOMNodeRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile) extends AsyncExtRunner(libs, code) with AbstractDOMNodeRunner - protected class ComDOMNodeRunner(libs: Seq[ResolvedJSDependency], code: VirtualJSFile) + protected class ComDOMNodeRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile) extends AsyncDOMNodeRunner(libs, code) with NodeComJSRunner protected trait AbstractDOMNodeRunner extends AbstractNodeRunner { @@ -95,9 +94,9 @@ class JSDOMNodeJSEnv( override protected def getJSFiles(): Seq[VirtualJSFile] = initFiles() ++ customInitFiles() ++ codeWithJSDOMContext() - /** Libraries are loaded via scripts in Node.js */ + /** Libraries are loaded via scripts in the jsdom environment. */ override protected def getLibJSFiles(): Seq[VirtualJSFile] = - libs.map(_.lib) + libs // Send code to Stdin override protected def sendVMStdin(out: OutputStream): Unit = { From b568d643cdefd6b651057c065da5e572ed564614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 5 Apr 2017 14:54:05 +0200 Subject: [PATCH 07/16] Do not use jsDependencies (nor jQuery) in sbt-plugin-test/withDOM. This turns withDOM into a test for DOM support only, independent of `jsDependencies`. --- .../withDOM/src/main/scala/sbttest/withDOM/Lib.scala | 11 +---------- .../src/main/scala/sbttest/withDOM/TestApp.scala | 2 +- .../src/test/scala/sbttest/withDOM/LibTest.scala | 7 ++----- 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/Lib.scala b/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/Lib.scala index e431557..4c22036 100644 --- a/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/Lib.scala +++ b/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/Lib.scala @@ -5,24 +5,15 @@ import scala.scalajs.js object Lib { val document: js.Dynamic = js.Dynamic.global.document - val jQuery: js.Dynamic = js.Dynamic.global.jQuery def getElementsByTagName(name: String): js.Array[js.Dynamic] = document.getElementsByTagName(name).asInstanceOf[js.Array[js.Dynamic]] /** appends a

with the message to the document */ def appendDocument(msg: String): Unit = { - val trg = { - val bodies = getElementsByTagName("body") - if (bodies.length > 0) - bodies(0) - else - document - } - val elem = document.createElement("p") elem.appendChild(document.createTextNode(msg)) - trg.appendChild(elem) + document.body.appendChild(elem) } } diff --git a/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/TestApp.scala b/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/TestApp.scala index e61ed20..f614bc6 100644 --- a/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/TestApp.scala +++ b/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/TestApp.scala @@ -8,7 +8,7 @@ object TestApp extends js.JSApp { Lib.appendDocument("Hello World") Lib.appendDocument("Still Here!") - println(Lib.jQuery("p").text()) + println(Lib.getElementsByTagName("p").head.innerHTML) } } diff --git a/sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala b/sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala index d8b11d0..174ec87 100644 --- a/sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala +++ b/sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala @@ -6,12 +6,9 @@ import org.junit.Test import org.junit.Assert._ class LibTest { - @Test def dummy_library_should_provide_jQuery(): Unit = { - assertFalse(js.isUndefined(Lib.jQuery)) - } - @Test def dummy_library_should_append_an_element(): Unit = { - def count = Lib.jQuery("p").length.asInstanceOf[Int] + def count = Lib.getElementsByTagName("p").length + val oldCount = count Lib.appendDocument("foo") assertEquals(1, count - oldCount) From 4a75741f689974a100b96e4275334671e5c94d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Sat, 8 Apr 2017 10:29:04 +0200 Subject: [PATCH 08/16] Unify the parameter names of external JS env constructors. They all standardize on `executable`, `args` en `env`, which are the names used by the `Initialize` constructors in `ScalaJSPlugin`. --- .../org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala index cc0d4cc..4da372e 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala @@ -18,10 +18,13 @@ import org.scalajs.jsenv._ import org.scalajs.core.ir.Utils.escapeJS class JSDOMNodeJSEnv( - nodejsPath: String = "node", - addArgs: Seq[String] = Seq.empty, - addEnv: Map[String, String] = Map.empty -) extends AbstractNodeJSEnv(nodejsPath, addArgs, addEnv, sourceMap = false) { + @deprecatedName('nodejsPath) + executable: String = "node", + @deprecatedName('addArgs) + args: Seq[String] = Seq.empty, + @deprecatedName('addEnv) + env: Map[String, String] = Map.empty) + extends AbstractNodeJSEnv(executable, args, env, sourceMap = false) { protected def vmName: String = "Node.js with JSDOM" From 703ec63e05fdd4aa12b9d6543247f9aeac6636f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Mon, 10 Apr 2017 18:15:17 +0200 Subject: [PATCH 09/16] Fix scala-js/scala-js#2874: Remove the distinction libs/code in the JSEnv API. `JSEnv#jsRunner` and friends `asyncRunner` and `comRunner` now only take a `Seq[VirtualJSFiles]`, containing all JS files to be given to the JS environment. The JS environments therefore make no distinction between "library" files and the "code" file. The disinction was arbitrary anyway, and unifying them makes sure that all the JS files are first-class. In particular, it forced to solve shortcomings in terms of error handling in `JSDOMNodeJSEnv`. --- .../scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala | 90 +++++++++++-------- 1 file changed, 53 insertions(+), 37 deletions(-) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala index a399104..2ab94a1 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala @@ -27,66 +27,66 @@ class JSDOMNodeJSEnv( protected def vmName: String = "Node.js with JSDOM" - override def jsRunner(libs: Seq[VirtualJSFile], - code: VirtualJSFile): JSRunner = { - new DOMNodeRunner(libs, code) - } + override def jsRunner(files: Seq[VirtualJSFile]): JSRunner = + new DOMNodeRunner(files) - override def asyncRunner(libs: Seq[VirtualJSFile], - code: VirtualJSFile): AsyncJSRunner = { - new AsyncDOMNodeRunner(libs, code) - } + override def asyncRunner(files: Seq[VirtualJSFile]): AsyncJSRunner = + new AsyncDOMNodeRunner(files) - override def comRunner(libs: Seq[VirtualJSFile], - code: VirtualJSFile): ComJSRunner = { - new ComDOMNodeRunner(libs, code) - } + override def comRunner(files: Seq[VirtualJSFile]): ComJSRunner = + new ComDOMNodeRunner(files) - protected class DOMNodeRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile) - extends ExtRunner(libs, code) with AbstractDOMNodeRunner + protected class DOMNodeRunner(files: Seq[VirtualJSFile]) + extends ExtRunner(files) with AbstractDOMNodeRunner - protected class AsyncDOMNodeRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile) - extends AsyncExtRunner(libs, code) with AbstractDOMNodeRunner + protected class AsyncDOMNodeRunner(files: Seq[VirtualJSFile]) + extends AsyncExtRunner(files) with AbstractDOMNodeRunner - protected class ComDOMNodeRunner(libs: Seq[VirtualJSFile], code: VirtualJSFile) - extends AsyncDOMNodeRunner(libs, code) with NodeComJSRunner + protected class ComDOMNodeRunner(files: Seq[VirtualJSFile]) + extends AsyncDOMNodeRunner(files) with NodeComJSRunner protected trait AbstractDOMNodeRunner extends AbstractNodeRunner { protected def codeWithJSDOMContext(): Seq[VirtualJSFile] = { - val scriptsJSPaths = getLibJSFiles().map { + val scriptsPaths = getScriptsJSFiles().map { case file: FileVirtualFile => file.path case file => libCache.materialize(file).getAbsolutePath } - val scriptsStringPath = scriptsJSPaths.map('"' + escapeJS(_) + '"') + val scriptsURIs = + scriptsPaths.map(path => new java.io.File(path).toURI.toASCIIString) + val scriptsURIsAsJSStrings = scriptsURIs.map('"' + escapeJS(_) + '"') val jsDOMCode = { s""" |(function () { | const jsdom = require("jsdom"); - | var windowKeys = []; + | + | var virtualConsole = jsdom.createVirtualConsole() + | .sendTo(console, { omitJsdomErrors: true }); + | virtualConsole.on("jsdomError", function (error) { + | /* This inelegant if + console.error is the only way I found + | * to make sure the stack trace of the original error is + | * printed out. + | */ + | if (error.detail && error.detail.stack) + | console.error(error.detail.stack); + | + | // Throw the error anew to make sure the whole execution fails + | throw error; + | }); | | jsdom.env({ | html: "", - | virtualConsole: jsdom.createVirtualConsole().sendTo(console), + | url: "http://localhost/", + | virtualConsole: virtualConsole, | created: function (error, window) { | if (error == null) { | window["__ScalaJSEnv"] = __ScalaJSEnv; | window["scalajsCom"] = global.scalajsCom; - | windowKeys = Object.keys(window); | } else { - | console.log(error); + | throw error; | } | }, - | scripts: [${scriptsStringPath.mkString(", ")}], - | onload: function (window) { - | jsdom.changeURL(window, "http://localhost"); - | for (var k in window) { - | if (windowKeys.indexOf(k) == -1) - | global[k] = window[k]; - | } - | - | ${code.content} - | } + | scripts: [${scriptsURIsAsJSStrings.mkString(", ")}] | }); |})(); |""".stripMargin @@ -94,12 +94,28 @@ class JSDOMNodeJSEnv( Seq(new MemVirtualJSFile("codeWithJSDOMContext.js").withContent(jsDOMCode)) } + /** All the JS files that are passed to the VM. + * + * This method can overridden to provide custom behavior in subclasses. + * + * This method is overridden in `JSDOMNodeJSEnv` so that user-provided + * JS files (excluding "init" files) are executed as *scripts* within the + * jsdom environment, rather than being directly executed by the VM. + * + * The value returned by this method in `JSDOMNodeJSEnv` is + * `initFiles() ++ customInitFiles() ++ codeWithJSDOMContext()`. + */ override protected def getJSFiles(): Seq[VirtualJSFile] = initFiles() ++ customInitFiles() ++ codeWithJSDOMContext() - /** Libraries are loaded via scripts in the jsdom environment. */ - override protected def getLibJSFiles(): Seq[VirtualJSFile] = - libs + /** JS files to be loaded via scripts in the jsdom environment. + * + * This method can be overridden to provide a different list of scripts. + * + * The default value in `JSDOMNodeJSEnv` is `files`. + */ + protected def getScriptsJSFiles(): Seq[VirtualJSFile] = + files // Send code to Stdin override protected def sendVMStdin(out: OutputStream): Unit = { From f582d26af9cd08bcd88ab75fb8e64ef498385c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 26 Apr 2017 01:49:31 +0200 Subject: [PATCH 10/16] Fix scala-js/scala-js#2902: Adapt to jsdom v10. While still retaining compatibility with jsdom v9. --- .../scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala index 4da372e..df13d0d 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala @@ -63,7 +63,13 @@ class JSDOMNodeJSEnv( val jsDOMCode = { s""" |(function () { - | const jsdom = require("jsdom"); + | var jsdom; + | try { + | jsdom = require("jsdom/lib/old-api.js"); // jsdom >= 10.x + | } catch (e) { + | jsdom = require("jsdom"); // jsdom <= 9.x + | } + | | var windowKeys = []; | | jsdom.env({ From ca40d6cf3961bae65eb7a2598ee04ecbbf79d111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Wed, 7 Jun 2017 17:03:33 +0200 Subject: [PATCH 11/16] Recognize any "standard" JVM-style main object in the sbt plugin. In addition to recognizing objects extending `js.JSApp`, the sbt plugin now recognizes "standard" main methods as well, i.e., objects with a method of the form def main(args: Array[String]): Unit = ... If such an object is used as main class, its main method is called using the new `ModuleInitializer.mainMethodWithArgs`. This has the nice benefit that we can finally write a cross-platform main object. We "softly" deprecate `js.JSApp`: its documentation says not to use anymore, but it does not cause a deprecation warning yet. For backward compatibility, objects extending `js.JSApp` are still recognized, and their `main(): Unit` method will be called. There are some subtle scenarios regarding backward compatibility: * If an object *both* extends `js.JSApp` and has a standard main method, the `main()` method of `js.JSApp` is preferred, for backward compatibility. * If an object has a `main(): Unit` method but does not extend `js.JSApp`, it will not be *discovered*. In that case, we assume the old style and call `main()`. This can happen if the user explicitly sets the `mainClass` setting. * If an object has *both* a `main(): Unit` method and a `main(args: Array[String]): Unit` method, it *will* now be discovered, and the new style will be assumed. This can potentially break compatibility, as now the standard main will be called. This only happens in case the user has explicitly set `mainClass`. Moreover, it is only problematic if `main()` does not behave the same as `main(Array())`, in which case the codebase is arguably in a bad shape to begin with. * The very fact of recognizing *more* objects as potential main objects means that the sbt build can break because it cannot automatically choose between several discovered objects. This will however fail to link with a nice error message, so it is not too bad. Here are some examples. * Old-style only, `main()` is selected object OldStyleOnly extends js.JSApp { def main(): Unit = ... } * New-style only, `main(Array[String])` is selected object NewStyleOnly { def main(args: Array[String]): Unit = ... } * Old and new style combined, `main()` is selected object OldAndNewStyle extends js.JSApp { def main(): Unit = ... def main(args: Array[String]): Unit = ... } * `def main(): Unit` without `js.JSApp` only, `main()` is selected object OldStyleNonJSApp { def main(): Unit = ... } * Both styles of methods without `js.JSApp`, `main(Array[String])` is selected (potentially silently breaking) object OldAndNewStyleNonJSApp { def main(): Unit = ... def main(args: Array[String]): Unit = ... } * Both styles exist in two different objects, sbt reports an ambiguity and demands that `mainClass` be set explicitly object OldStyle extends js.JSApp { def main(): Unit = ... } object NewStyle { def main(args: Array[String]): Unit = ... } --- .../withDOM/src/main/scala/sbttest/withDOM/TestApp.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/TestApp.scala b/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/TestApp.scala index e61ed20..4c91bba 100644 --- a/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/TestApp.scala +++ b/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/TestApp.scala @@ -1,10 +1,8 @@ package sbttest.withDOM -import scala.scalajs.js +object TestApp { -object TestApp extends js.JSApp { - - def main(): Unit = { + def main(args: Array[String]): Unit = { Lib.appendDocument("Hello World") Lib.appendDocument("Still Here!") From f06ed90171f89218ab70fe40b6da096a143d06c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 9 Jun 2017 16:08:31 +0200 Subject: [PATCH 12/16] Introduce a new surface API for JS envs with `Config` objects. This only changes the surface API of concrete JS envs. Their existing constructors are deprecated in favor of an overload with a `Config` object. This change provides in the 0.6.x series an API that can be used in a source-compatible way between 0.6.x and 1.x. In 1.x, deeper changes to the internal API of JS envs will be done. We also take this opportunity to "move" `JSDOMNodeJSEnv` in a different package `org.scalajs.jsenv.jsdomnodejs`. Since this JS env is scheduled to be moved in a different repository in 1.x, it should eventually be in a different package anyway. --- .../jsenv/test/JSDOMNodeJSEnvTest.scala | 2 +- .../jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala | 61 +++++++++++++++++++ .../scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala | 26 +++++--- 3 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 js-envs/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala diff --git a/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/JSDOMNodeJSEnvTest.scala b/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/JSDOMNodeJSEnvTest.scala index 88d8e19..5be81d5 100644 --- a/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/JSDOMNodeJSEnvTest.scala +++ b/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/JSDOMNodeJSEnvTest.scala @@ -1,6 +1,6 @@ package org.scalajs.jsenv.test -import org.scalajs.jsenv.nodejs.JSDOMNodeJSEnv +import org.scalajs.jsenv.jsdomnodejs.JSDOMNodeJSEnv import org.junit.Test import org.junit.Assert._ diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala new file mode 100644 index 0000000..e696fa4 --- /dev/null +++ b/js-envs/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala @@ -0,0 +1,61 @@ +/* __ *\ +** ________ ___ / / ___ __ ____ Scala.js JS envs ** +** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2017, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** +** /____/\___/_/ |_/____/_/ | |__/ /____/ ** +** |/____/ ** +\* */ + +package org.scalajs.jsenv.jsdomnodejs + +class JSDOMNodeJSEnv(config: JSDOMNodeJSEnv.Config) + extends org.scalajs.jsenv.nodejs.JSDOMNodeJSEnv(config.executable, + config.args, config.env, internal = ()) { + + def this() = this(JSDOMNodeJSEnv.Config()) +} + +object JSDOMNodeJSEnv { + final class Config private ( + val executable: String, + val args: List[String], + val env: Map[String, String] + ) { + private def this() = { + this( + executable = "node", + args = Nil, + env = Map.empty + ) + } + + def withExecutable(executable: String): Config = + copy(executable = executable) + + def withArgs(args: List[String]): Config = + copy(args = args) + + def withEnv(env: Map[String, String]): Config = + copy(env = env) + + private def copy( + executable: String = executable, + args: List[String] = args, + env: Map[String, String] = env + ): Config = { + new Config(executable, args, env) + } + } + + object Config { + /** Returns a default configuration for a [[JSDOMNodeJSEnv]]. + * + * The defaults are: + * + * - `executable`: `"node"` + * - `args`: `Nil` + * - `env`: `Map.empty` + */ + def apply(): Config = new Config() + } +} diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala index df13d0d..a19c62a 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala @@ -17,14 +17,24 @@ import org.scalajs.jsenv._ import org.scalajs.core.ir.Utils.escapeJS -class JSDOMNodeJSEnv( - @deprecatedName('nodejsPath) - executable: String = "node", - @deprecatedName('addArgs) - args: Seq[String] = Seq.empty, - @deprecatedName('addEnv) - env: Map[String, String] = Map.empty) - extends AbstractNodeJSEnv(executable, args, env, sourceMap = false) { +class JSDOMNodeJSEnv private[jsenv] ( + executable: String, + args: Seq[String], + env: Map[String, String], + internal: Unit +) extends AbstractNodeJSEnv(executable, args, env, sourceMap = false) { + + @deprecated("Use org.scalajs.jsenv.jsdomnodejs.JSDOMNodeJSEnv.", "0.6.18") + def this( + @deprecatedName('nodejsPath) + executable: String = "node", + @deprecatedName('addArgs) + args: Seq[String] = Seq.empty, + @deprecatedName('addEnv) + env: Map[String, String] = Map.empty + ) = { + this(executable, args, env, internal = ()) + } protected def vmName: String = "Node.js with JSDOM" From c354793d08456c994d4c5a0f27d00801af7af023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 16 Jun 2017 14:05:30 +0200 Subject: [PATCH 13/16] Remove the deprecated APIs in JS envs. In the process, the implementation of `org.scalajs.jsenv.nodejs.JSDOMNodeJSEnv` is transferred to the new `org.scalajs.jsenv.jsdomnodejs.JSDOMNodeJSEnv`. --- .../jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala | 119 +++++++++++++- .../scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala | 145 ------------------ 2 files changed, 117 insertions(+), 147 deletions(-) delete mode 100644 js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala index e696fa4..616522a 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala @@ -8,11 +8,126 @@ package org.scalajs.jsenv.jsdomnodejs +import java.io.OutputStream + +import org.scalajs.core.tools.io._ +import org.scalajs.jsenv._ +import org.scalajs.jsenv.nodejs.AbstractNodeJSEnv + +import org.scalajs.core.ir.Utils.escapeJS + class JSDOMNodeJSEnv(config: JSDOMNodeJSEnv.Config) - extends org.scalajs.jsenv.nodejs.JSDOMNodeJSEnv(config.executable, - config.args, config.env, internal = ()) { + extends AbstractNodeJSEnv(config.executable, config.args, config.env, + sourceMap = false) { def this() = this(JSDOMNodeJSEnv.Config()) + + protected def vmName: String = "Node.js with JSDOM" + + override def jsRunner(files: Seq[VirtualJSFile]): JSRunner = + new DOMNodeRunner(files) + + override def asyncRunner(files: Seq[VirtualJSFile]): AsyncJSRunner = + new AsyncDOMNodeRunner(files) + + override def comRunner(files: Seq[VirtualJSFile]): ComJSRunner = + new ComDOMNodeRunner(files) + + protected class DOMNodeRunner(files: Seq[VirtualJSFile]) + extends ExtRunner(files) with AbstractDOMNodeRunner + + protected class AsyncDOMNodeRunner(files: Seq[VirtualJSFile]) + extends AsyncExtRunner(files) with AbstractDOMNodeRunner + + protected class ComDOMNodeRunner(files: Seq[VirtualJSFile]) + extends AsyncDOMNodeRunner(files) with NodeComJSRunner + + protected trait AbstractDOMNodeRunner extends AbstractNodeRunner { + + protected def codeWithJSDOMContext(): Seq[VirtualJSFile] = { + val scriptsPaths = getScriptsJSFiles().map { + case file: FileVirtualFile => file.path + case file => libCache.materialize(file).getAbsolutePath + } + val scriptsURIs = + scriptsPaths.map(path => new java.io.File(path).toURI.toASCIIString) + val scriptsURIsAsJSStrings = scriptsURIs.map('"' + escapeJS(_) + '"') + val jsDOMCode = { + s""" + |(function () { + | var jsdom; + | try { + | jsdom = require("jsdom/lib/old-api.js"); // jsdom >= 10.x + | } catch (e) { + | jsdom = require("jsdom"); // jsdom <= 9.x + | } + | + | var virtualConsole = jsdom.createVirtualConsole() + | .sendTo(console, { omitJsdomErrors: true }); + | virtualConsole.on("jsdomError", function (error) { + | /* This inelegant if + console.error is the only way I found + | * to make sure the stack trace of the original error is + | * printed out. + | */ + | if (error.detail && error.detail.stack) + | console.error(error.detail.stack); + | + | // Throw the error anew to make sure the whole execution fails + | throw error; + | }); + | + | jsdom.env({ + | html: "", + | url: "http://localhost/", + | virtualConsole: virtualConsole, + | created: function (error, window) { + | if (error == null) { + | window["__ScalaJSEnv"] = __ScalaJSEnv; + | window["scalajsCom"] = global.scalajsCom; + | } else { + | throw error; + | } + | }, + | scripts: [${scriptsURIsAsJSStrings.mkString(", ")}] + | }); + |})(); + |""".stripMargin + } + Seq(new MemVirtualJSFile("codeWithJSDOMContext.js").withContent(jsDOMCode)) + } + + /** All the JS files that are passed to the VM. + * + * This method can overridden to provide custom behavior in subclasses. + * + * This method is overridden in `JSDOMNodeJSEnv` so that user-provided + * JS files (excluding "init" files) are executed as *scripts* within the + * jsdom environment, rather than being directly executed by the VM. + * + * The value returned by this method in `JSDOMNodeJSEnv` is + * `initFiles() ++ customInitFiles() ++ codeWithJSDOMContext()`. + */ + override protected def getJSFiles(): Seq[VirtualJSFile] = + initFiles() ++ customInitFiles() ++ codeWithJSDOMContext() + + /** JS files to be loaded via scripts in the jsdom environment. + * + * This method can be overridden to provide a different list of scripts. + * + * The default value in `JSDOMNodeJSEnv` is `files`. + */ + protected def getScriptsJSFiles(): Seq[VirtualJSFile] = + files + + // Send code to Stdin + override protected def sendVMStdin(out: OutputStream): Unit = { + /* Do not factor this method out into AbstractNodeRunner or when mixin in + * the traits it would use AbstractExtRunner.sendVMStdin due to + * linearization order. + */ + sendJS(getJSFiles(), out) + } + } } object JSDOMNodeJSEnv { diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala deleted file mode 100644 index 98fef40..0000000 --- a/js-envs/src/main/scala/org/scalajs/jsenv/nodejs/JSDOMNodeJSEnv.scala +++ /dev/null @@ -1,145 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** -** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** -** /____/\___/_/ |_/____/_/ | |__/ /____/ ** -** |/____/ ** -\* */ - - -package org.scalajs.jsenv.nodejs - -import java.io.{Console => _, _} - -import org.scalajs.core.tools.io._ -import org.scalajs.jsenv._ - -import org.scalajs.core.ir.Utils.escapeJS - -class JSDOMNodeJSEnv private[jsenv] ( - executable: String, - args: Seq[String], - env: Map[String, String], - internal: Unit -) extends AbstractNodeJSEnv(executable, args, env, sourceMap = false) { - - @deprecated("Use org.scalajs.jsenv.jsdomnodejs.JSDOMNodeJSEnv.", "0.6.18") - def this( - @deprecatedName('nodejsPath) - executable: String = "node", - @deprecatedName('addArgs) - args: Seq[String] = Seq.empty, - @deprecatedName('addEnv) - env: Map[String, String] = Map.empty - ) = { - this(executable, args, env, internal = ()) - } - - protected def vmName: String = "Node.js with JSDOM" - - override def jsRunner(files: Seq[VirtualJSFile]): JSRunner = - new DOMNodeRunner(files) - - override def asyncRunner(files: Seq[VirtualJSFile]): AsyncJSRunner = - new AsyncDOMNodeRunner(files) - - override def comRunner(files: Seq[VirtualJSFile]): ComJSRunner = - new ComDOMNodeRunner(files) - - protected class DOMNodeRunner(files: Seq[VirtualJSFile]) - extends ExtRunner(files) with AbstractDOMNodeRunner - - protected class AsyncDOMNodeRunner(files: Seq[VirtualJSFile]) - extends AsyncExtRunner(files) with AbstractDOMNodeRunner - - protected class ComDOMNodeRunner(files: Seq[VirtualJSFile]) - extends AsyncDOMNodeRunner(files) with NodeComJSRunner - - protected trait AbstractDOMNodeRunner extends AbstractNodeRunner { - - protected def codeWithJSDOMContext(): Seq[VirtualJSFile] = { - val scriptsPaths = getScriptsJSFiles().map { - case file: FileVirtualFile => file.path - case file => libCache.materialize(file).getAbsolutePath - } - val scriptsURIs = - scriptsPaths.map(path => new java.io.File(path).toURI.toASCIIString) - val scriptsURIsAsJSStrings = scriptsURIs.map('"' + escapeJS(_) + '"') - val jsDOMCode = { - s""" - |(function () { - | var jsdom; - | try { - | jsdom = require("jsdom/lib/old-api.js"); // jsdom >= 10.x - | } catch (e) { - | jsdom = require("jsdom"); // jsdom <= 9.x - | } - | - | var virtualConsole = jsdom.createVirtualConsole() - | .sendTo(console, { omitJsdomErrors: true }); - | virtualConsole.on("jsdomError", function (error) { - | /* This inelegant if + console.error is the only way I found - | * to make sure the stack trace of the original error is - | * printed out. - | */ - | if (error.detail && error.detail.stack) - | console.error(error.detail.stack); - | - | // Throw the error anew to make sure the whole execution fails - | throw error; - | }); - | - | jsdom.env({ - | html: "", - | url: "http://localhost/", - | virtualConsole: virtualConsole, - | created: function (error, window) { - | if (error == null) { - | window["__ScalaJSEnv"] = __ScalaJSEnv; - | window["scalajsCom"] = global.scalajsCom; - | } else { - | throw error; - | } - | }, - | scripts: [${scriptsURIsAsJSStrings.mkString(", ")}] - | }); - |})(); - |""".stripMargin - } - Seq(new MemVirtualJSFile("codeWithJSDOMContext.js").withContent(jsDOMCode)) - } - - /** All the JS files that are passed to the VM. - * - * This method can overridden to provide custom behavior in subclasses. - * - * This method is overridden in `JSDOMNodeJSEnv` so that user-provided - * JS files (excluding "init" files) are executed as *scripts* within the - * jsdom environment, rather than being directly executed by the VM. - * - * The value returned by this method in `JSDOMNodeJSEnv` is - * `initFiles() ++ customInitFiles() ++ codeWithJSDOMContext()`. - */ - override protected def getJSFiles(): Seq[VirtualJSFile] = - initFiles() ++ customInitFiles() ++ codeWithJSDOMContext() - - /** JS files to be loaded via scripts in the jsdom environment. - * - * This method can be overridden to provide a different list of scripts. - * - * The default value in `JSDOMNodeJSEnv` is `files`. - */ - protected def getScriptsJSFiles(): Seq[VirtualJSFile] = - files - - // Send code to Stdin - override protected def sendVMStdin(out: OutputStream): Unit = { - /* Do not factor this method out into AbstractNodeRunner or when mixin in - * the traits it would use AbstractExtRunner.sendVMStdin due to - * linearization order. - */ - sendJS(getJSFiles(), out) - } - } - -} From 75016669a5e1b96209ba81f9c65109f01b263757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 16 Jun 2017 14:20:18 +0200 Subject: [PATCH 14/16] Fix scala-js/scala-js#2877: Internal JS envs config amenable to bincompat evolution. The internal configuration of (abstract) classes in the JS env API is now `protected def`-based, rather than based on constructor parameters. This can be evolved in binary-compatible ways, as we can add new `protected def`s with default implementations in the superclasses. --- .../jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala b/js-envs/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala index 616522a..ac6fbab 100644 --- a/js-envs/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala +++ b/js-envs/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala @@ -8,6 +8,8 @@ package org.scalajs.jsenv.jsdomnodejs +import scala.collection.immutable + import java.io.OutputStream import org.scalajs.core.tools.io._ @@ -16,14 +18,21 @@ import org.scalajs.jsenv.nodejs.AbstractNodeJSEnv import org.scalajs.core.ir.Utils.escapeJS -class JSDOMNodeJSEnv(config: JSDOMNodeJSEnv.Config) - extends AbstractNodeJSEnv(config.executable, config.args, config.env, - sourceMap = false) { +class JSDOMNodeJSEnv(config: JSDOMNodeJSEnv.Config) extends AbstractNodeJSEnv { def this() = this(JSDOMNodeJSEnv.Config()) protected def vmName: String = "Node.js with JSDOM" + protected def executable: String = config.executable + + override protected def args: immutable.Seq[String] = config.args + + override protected def env: Map[String, String] = config.env + + // TODO We might want to make this configurable - not sure why it isn't + override protected def wantSourceMap: Boolean = false + override def jsRunner(files: Seq[VirtualJSFile]): JSRunner = new DOMNodeRunner(files) From 1c8cede1755f350d8b8ff2dfad2bb949536d44df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Thu, 22 Jun 2017 16:45:24 +0200 Subject: [PATCH 15/16] Extract JSDOMNodeJSEnv in its own project. So that we can move it to a different repository afterwards. In the process, we also move the standard `NodeJSEnv` in a dedicated project, although this one will obviously stay in the same repository. Now that all JS environments are in different artifacts, it makes sense that Node.js have its own artifact as well, even though the sbt plugin will depend on that artifact. The separation has a nice benefit that the `jsEnvsTestSuite` is not necessary anymore, since its last tests can be moved to the test directories of the relevant JS env implementations. --- .../scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala | 0 .../org/scalajs/jsenv/jsdomnodejs}/JSDOMNodeJSEnvTest.scala | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename {js-envs => jsdom-nodejs-env}/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala (100%) rename {js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test => jsdom-nodejs-env/src/test/scala/org/scalajs/jsenv/jsdomnodejs}/JSDOMNodeJSEnvTest.scala (84%) diff --git a/js-envs/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala b/jsdom-nodejs-env/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala similarity index 100% rename from js-envs/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala rename to jsdom-nodejs-env/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala diff --git a/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/JSDOMNodeJSEnvTest.scala b/jsdom-nodejs-env/src/test/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnvTest.scala similarity index 84% rename from js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/JSDOMNodeJSEnvTest.scala rename to jsdom-nodejs-env/src/test/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnvTest.scala index 5be81d5..0957c6e 100644 --- a/js-envs-test-suite/src/test/scala/org/scalajs/jsenv/test/JSDOMNodeJSEnvTest.scala +++ b/jsdom-nodejs-env/src/test/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnvTest.scala @@ -1,6 +1,6 @@ -package org.scalajs.jsenv.test +package org.scalajs.jsenv.jsdomnodejs -import org.scalajs.jsenv.jsdomnodejs.JSDOMNodeJSEnv +import org.scalajs.jsenv.test._ import org.junit.Test import org.junit.Assert._ From 62df6b6ac3840e41270e078f91aef7f5426ddbb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Doeraene?= Date: Fri, 23 Jun 2017 16:33:06 +0200 Subject: [PATCH 16/16] Import the Node.js with jsdom env from the Scala.js core repository. This is the initial import of the Node.js with jsdom env from the Scala.js core repository. The history of this commit reflects the entire history of relevant files from the core repo, filter-branch'ed to appear as if they had always been in this repo. This commit adds the specific setup of the build and tests. --- .travis.yml | 41 +++++++++ build.sbt | 88 +++++++++++++++++++ project/build.properties | 1 + project/plugins.sbt | 6 ++ .../src/main/scala/testproject}/Lib.scala | 2 +- .../src/main/scala/testproject}/TestApp.scala | 2 +- .../src/test/scala/testproject}/LibTest.scala | 2 +- 7 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 .travis.yml create mode 100644 build.sbt create mode 100644 project/build.properties create mode 100644 project/plugins.sbt rename {sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM => test-project/src/main/scala/testproject}/Lib.scala (95%) rename {sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM => test-project/src/main/scala/testproject}/TestApp.scala (89%) rename {sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM => test-project/src/test/scala/testproject}/LibTest.scala (92%) diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..dbece55 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,41 @@ +sudo: false +language: scala +scala: + - 2.10.6 + - 2.11.11 + - 2.12.2 +jdk: + - oraclejdk8 +env: + - JSDOM_VERSION=9.12.0 + - JSDOM_VERSION=10.0.0 +install: + # The default ivy resolution takes way too much time, and times out Travis builds. + # We use coursier instead. + - mkdir -p $HOME/.sbt/0.13/plugins/ + - echo 'addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-RC3")' > $HOME/.sbt/0.13/plugins/coursier.sbt + # We need a recent version of Node.js for jsdom + - nvm install 6 + - nvm use 6 + - node --version + # Of course we need jsdom + - npm install jsdom@$JSDOM_VERSION + # While there is no published version of Scala.js 1.x, we have to locally build a snapshot. + - git clone https://github.com/scala-js/scala-js.git + - cd scala-js + - git checkout d25fa8bba708977c68c093fdbc50958368f9602f + - sbt ++$TRAVIS_SCALA_VERSION compiler/publishLocal jUnitPlugin/publishLocal library/publishLocal testInterface/publishLocal jUnitRuntime/publishLocal ir/publishLocal tools/publishLocal jsEnvs/publishLocal jsEnvsTestKit/publishLocal nodeJSEnv/publishLocal + - sbt ++2.10.6 ir/publishLocal tools/publishLocal jsEnvs/publishLocal nodeJSEnv/publishLocal testAdapter/publishLocal sbtPlugin/publishLocal + - cd .. +script: + - sbt ++$TRAVIS_SCALA_VERSION scalajs-env-jsdom-nodejs/test scalajs-env-jsdom-nodejs/doc + - sbt ++$TRAVIS_SCALA_VERSION test-project/run test-project/test +cache: + directories: + - $HOME/.ivy2/cache + - $HOME/.sbt + - $HOME/.coursier/cache +before_cache: + - find $HOME/.ivy2/cache -name "ivydata-*.properties" -print -delete + - find $HOME/.sbt -name "*.lock" -print -delete + - rm $HOME/.sbt/0.13/plugins/coursier.sbt diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..511eb75 --- /dev/null +++ b/build.sbt @@ -0,0 +1,88 @@ +inThisBuild(Seq( + version := "0.1.0-SNAPSHOT", + organization := "org.scala-js", + + crossScalaVersions := Seq("2.10.6", "2.11.11", "2.12.2"), + scalaVersion := "2.11.11", + scalacOptions ++= Seq("-deprecation", "-feature", "-Xfatal-warnings"), + + homepage := Some(url("https://www.scala-js.org/")), + licenses += ("BSD New", + url("https://github.com/scala-js/scala-js-env-jsdom-nodejs/blob/master/LICENSE")), + scmInfo := Some(ScmInfo( + url("https://github.com/scala-js/scala-js-env-jsdom-nodejs"), + "scm:git:git@github.com:scala-js/scala-js-env-jsdom-nodejs.git", + Some("scm:git:git@github.com:scala-js/scala-js-env-jsdom-nodejs.git"))) +)) + +val commonSettings = Def.settings( + // Scaladoc linking + apiURL := { + val name = moduleName.value + val v = version.value + Some(url(s"https://www.scala-js.org/api/$name/$v/")) + }, + autoAPIMappings := true, + + publishMavenStyle := true, + publishTo := { + val nexus = "https://oss.sonatype.org/" + if (isSnapshot.value) + Some("snapshots" at nexus + "content/repositories/snapshots") + else + Some("releases" at nexus + "service/local/staging/deploy/maven2") + }, + pomExtra := ( + + + sjrd + Sébastien Doeraene + https://github.com/sjrd/ + + + gzm0 + Tobias Schlatter + https://github.com/gzm0/ + + + nicolasstucki + Nicolas Stucki + https://github.com/nicolasstucki/ + + + ), + pomIncludeRepository := { _ => false } +) + +lazy val root: Project = project.in(file(".")). + settings( + publishArtifact in Compile := false, + publish := {}, + publishLocal := {}, + + clean := clean.dependsOn( + clean in `scalajs-env-jsdom-nodejs`, + clean in `test-project` + ).value + ) + +lazy val `scalajs-env-jsdom-nodejs`: Project = project.in(file("jsdom-nodejs-env")). + settings( + commonSettings, + + libraryDependencies ++= Seq( + "org.scala-js" %% "scalajs-js-envs" % scalaJSVersion, + "org.scala-js" %% "scalajs-nodejs-env" % scalaJSVersion, + + "com.novocode" % "junit-interface" % "0.11" % "test", + "org.scala-js" %% "scalajs-js-envs-test-kit" % scalaJSVersion % "test" + ) + ) + +lazy val `test-project`: Project = project. + enablePlugins(ScalaJSPlugin). + enablePlugins(ScalaJSJUnitPlugin). + settings( + scalaJSUseMainModuleInitializer := true, + jsEnv := new org.scalajs.jsenv.jsdomnodejs.JSDOMNodeJSEnv() + ) diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..64317fd --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.15 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..ab8517d --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1,6 @@ +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.0.0-SNAPSHOT") + +libraryDependencies += "org.scala-js" %% "scalajs-nodejs-env" % "1.0.0-SNAPSHOT" + +unmanagedSourceDirectories in Compile += + baseDirectory.value.getParentFile / "jsdom-nodejs-env/src/main/scala" diff --git a/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/Lib.scala b/test-project/src/main/scala/testproject/Lib.scala similarity index 95% rename from sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/Lib.scala rename to test-project/src/main/scala/testproject/Lib.scala index 4c22036..e6d0913 100644 --- a/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/Lib.scala +++ b/test-project/src/main/scala/testproject/Lib.scala @@ -1,4 +1,4 @@ -package sbttest.withDOM +package testproject import scala.scalajs.js diff --git a/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/TestApp.scala b/test-project/src/main/scala/testproject/TestApp.scala similarity index 89% rename from sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/TestApp.scala rename to test-project/src/main/scala/testproject/TestApp.scala index 08e9cec..9c17e1f 100644 --- a/sbt-plugin-test/withDOM/src/main/scala/sbttest/withDOM/TestApp.scala +++ b/test-project/src/main/scala/testproject/TestApp.scala @@ -1,4 +1,4 @@ -package sbttest.withDOM +package testproject object TestApp { diff --git a/sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala b/test-project/src/test/scala/testproject/LibTest.scala similarity index 92% rename from sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala rename to test-project/src/test/scala/testproject/LibTest.scala index 174ec87..1bbe022 100644 --- a/sbt-plugin-test/withDOM/src/test/scala/sbttest/withDOM/LibTest.scala +++ b/test-project/src/test/scala/testproject/LibTest.scala @@ -1,4 +1,4 @@ -package sbttest.withDOM +package testproject import scala.scalajs.js