Skip to content

Commit 58b3784

Browse files
authored
Merge pull request #1 from sjrd/import-from-core
Import the Node.js with jsdom env from the Scala.js core repository.
2 parents ed359fb + 62df6b6 commit 58b3784

File tree

9 files changed

+391
-0
lines changed

9 files changed

+391
-0
lines changed

.travis.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
sudo: false
2+
language: scala
3+
scala:
4+
- 2.10.6
5+
- 2.11.11
6+
- 2.12.2
7+
jdk:
8+
- oraclejdk8
9+
env:
10+
- JSDOM_VERSION=9.12.0
11+
- JSDOM_VERSION=10.0.0
12+
install:
13+
# The default ivy resolution takes way too much time, and times out Travis builds.
14+
# We use coursier instead.
15+
- mkdir -p $HOME/.sbt/0.13/plugins/
16+
- echo 'addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-RC3")' > $HOME/.sbt/0.13/plugins/coursier.sbt
17+
# We need a recent version of Node.js for jsdom
18+
- nvm install 6
19+
- nvm use 6
20+
- node --version
21+
# Of course we need jsdom
22+
- npm install jsdom@$JSDOM_VERSION
23+
# While there is no published version of Scala.js 1.x, we have to locally build a snapshot.
24+
- git clone https://github.com/scala-js/scala-js.git
25+
- cd scala-js
26+
- git checkout d25fa8bba708977c68c093fdbc50958368f9602f
27+
- sbt ++$TRAVIS_SCALA_VERSION compiler/publishLocal jUnitPlugin/publishLocal library/publishLocal testInterface/publishLocal jUnitRuntime/publishLocal ir/publishLocal tools/publishLocal jsEnvs/publishLocal jsEnvsTestKit/publishLocal nodeJSEnv/publishLocal
28+
- sbt ++2.10.6 ir/publishLocal tools/publishLocal jsEnvs/publishLocal nodeJSEnv/publishLocal testAdapter/publishLocal sbtPlugin/publishLocal
29+
- cd ..
30+
script:
31+
- sbt ++$TRAVIS_SCALA_VERSION scalajs-env-jsdom-nodejs/test scalajs-env-jsdom-nodejs/doc
32+
- sbt ++$TRAVIS_SCALA_VERSION test-project/run test-project/test
33+
cache:
34+
directories:
35+
- $HOME/.ivy2/cache
36+
- $HOME/.sbt
37+
- $HOME/.coursier/cache
38+
before_cache:
39+
- find $HOME/.ivy2/cache -name "ivydata-*.properties" -print -delete
40+
- find $HOME/.sbt -name "*.lock" -print -delete
41+
- rm $HOME/.sbt/0.13/plugins/coursier.sbt

build.sbt

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
inThisBuild(Seq(
2+
version := "0.1.0-SNAPSHOT",
3+
organization := "org.scala-js",
4+
5+
crossScalaVersions := Seq("2.10.6", "2.11.11", "2.12.2"),
6+
scalaVersion := "2.11.11",
7+
scalacOptions ++= Seq("-deprecation", "-feature", "-Xfatal-warnings"),
8+
9+
homepage := Some(url("https://www.scala-js.org/")),
10+
licenses += ("BSD New",
11+
url("https://github.com/scala-js/scala-js-env-jsdom-nodejs/blob/master/LICENSE")),
12+
scmInfo := Some(ScmInfo(
13+
url("https://github.com/scala-js/scala-js-env-jsdom-nodejs"),
14+
"scm:git:git@github.com:scala-js/scala-js-env-jsdom-nodejs.git",
15+
Some("scm:git:git@github.com:scala-js/scala-js-env-jsdom-nodejs.git")))
16+
))
17+
18+
val commonSettings = Def.settings(
19+
// Scaladoc linking
20+
apiURL := {
21+
val name = moduleName.value
22+
val v = version.value
23+
Some(url(s"https://www.scala-js.org/api/$name/$v/"))
24+
},
25+
autoAPIMappings := true,
26+
27+
publishMavenStyle := true,
28+
publishTo := {
29+
val nexus = "https://oss.sonatype.org/"
30+
if (isSnapshot.value)
31+
Some("snapshots" at nexus + "content/repositories/snapshots")
32+
else
33+
Some("releases" at nexus + "service/local/staging/deploy/maven2")
34+
},
35+
pomExtra := (
36+
<developers>
37+
<developer>
38+
<id>sjrd</id>
39+
<name>Sébastien Doeraene</name>
40+
<url>https://github.com/sjrd/</url>
41+
</developer>
42+
<developer>
43+
<id>gzm0</id>
44+
<name>Tobias Schlatter</name>
45+
<url>https://github.com/gzm0/</url>
46+
</developer>
47+
<developer>
48+
<id>nicolasstucki</id>
49+
<name>Nicolas Stucki</name>
50+
<url>https://github.com/nicolasstucki/</url>
51+
</developer>
52+
</developers>
53+
),
54+
pomIncludeRepository := { _ => false }
55+
)
56+
57+
lazy val root: Project = project.in(file(".")).
58+
settings(
59+
publishArtifact in Compile := false,
60+
publish := {},
61+
publishLocal := {},
62+
63+
clean := clean.dependsOn(
64+
clean in `scalajs-env-jsdom-nodejs`,
65+
clean in `test-project`
66+
).value
67+
)
68+
69+
lazy val `scalajs-env-jsdom-nodejs`: Project = project.in(file("jsdom-nodejs-env")).
70+
settings(
71+
commonSettings,
72+
73+
libraryDependencies ++= Seq(
74+
"org.scala-js" %% "scalajs-js-envs" % scalaJSVersion,
75+
"org.scala-js" %% "scalajs-nodejs-env" % scalaJSVersion,
76+
77+
"com.novocode" % "junit-interface" % "0.11" % "test",
78+
"org.scala-js" %% "scalajs-js-envs-test-kit" % scalaJSVersion % "test"
79+
)
80+
)
81+
82+
lazy val `test-project`: Project = project.
83+
enablePlugins(ScalaJSPlugin).
84+
enablePlugins(ScalaJSJUnitPlugin).
85+
settings(
86+
scalaJSUseMainModuleInitializer := true,
87+
jsEnv := new org.scalajs.jsenv.jsdomnodejs.JSDOMNodeJSEnv()
88+
)
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/* __ *\
2+
** ________ ___ / / ___ __ ____ Scala.js JS envs **
3+
** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2017, LAMP/EPFL **
4+
** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
5+
** /____/\___/_/ |_/____/_/ | |__/ /____/ **
6+
** |/____/ **
7+
\* */
8+
9+
package org.scalajs.jsenv.jsdomnodejs
10+
11+
import scala.collection.immutable
12+
13+
import java.io.OutputStream
14+
15+
import org.scalajs.core.tools.io._
16+
import org.scalajs.jsenv._
17+
import org.scalajs.jsenv.nodejs.AbstractNodeJSEnv
18+
19+
import org.scalajs.core.ir.Utils.escapeJS
20+
21+
class JSDOMNodeJSEnv(config: JSDOMNodeJSEnv.Config) extends AbstractNodeJSEnv {
22+
23+
def this() = this(JSDOMNodeJSEnv.Config())
24+
25+
protected def vmName: String = "Node.js with JSDOM"
26+
27+
protected def executable: String = config.executable
28+
29+
override protected def args: immutable.Seq[String] = config.args
30+
31+
override protected def env: Map[String, String] = config.env
32+
33+
// TODO We might want to make this configurable - not sure why it isn't
34+
override protected def wantSourceMap: Boolean = false
35+
36+
override def jsRunner(files: Seq[VirtualJSFile]): JSRunner =
37+
new DOMNodeRunner(files)
38+
39+
override def asyncRunner(files: Seq[VirtualJSFile]): AsyncJSRunner =
40+
new AsyncDOMNodeRunner(files)
41+
42+
override def comRunner(files: Seq[VirtualJSFile]): ComJSRunner =
43+
new ComDOMNodeRunner(files)
44+
45+
protected class DOMNodeRunner(files: Seq[VirtualJSFile])
46+
extends ExtRunner(files) with AbstractDOMNodeRunner
47+
48+
protected class AsyncDOMNodeRunner(files: Seq[VirtualJSFile])
49+
extends AsyncExtRunner(files) with AbstractDOMNodeRunner
50+
51+
protected class ComDOMNodeRunner(files: Seq[VirtualJSFile])
52+
extends AsyncDOMNodeRunner(files) with NodeComJSRunner
53+
54+
protected trait AbstractDOMNodeRunner extends AbstractNodeRunner {
55+
56+
protected def codeWithJSDOMContext(): Seq[VirtualJSFile] = {
57+
val scriptsPaths = getScriptsJSFiles().map {
58+
case file: FileVirtualFile => file.path
59+
case file => libCache.materialize(file).getAbsolutePath
60+
}
61+
val scriptsURIs =
62+
scriptsPaths.map(path => new java.io.File(path).toURI.toASCIIString)
63+
val scriptsURIsAsJSStrings = scriptsURIs.map('"' + escapeJS(_) + '"')
64+
val jsDOMCode = {
65+
s"""
66+
|(function () {
67+
| var jsdom;
68+
| try {
69+
| jsdom = require("jsdom/lib/old-api.js"); // jsdom >= 10.x
70+
| } catch (e) {
71+
| jsdom = require("jsdom"); // jsdom <= 9.x
72+
| }
73+
|
74+
| var virtualConsole = jsdom.createVirtualConsole()
75+
| .sendTo(console, { omitJsdomErrors: true });
76+
| virtualConsole.on("jsdomError", function (error) {
77+
| /* This inelegant if + console.error is the only way I found
78+
| * to make sure the stack trace of the original error is
79+
| * printed out.
80+
| */
81+
| if (error.detail && error.detail.stack)
82+
| console.error(error.detail.stack);
83+
|
84+
| // Throw the error anew to make sure the whole execution fails
85+
| throw error;
86+
| });
87+
|
88+
| jsdom.env({
89+
| html: "",
90+
| url: "http://localhost/",
91+
| virtualConsole: virtualConsole,
92+
| created: function (error, window) {
93+
| if (error == null) {
94+
| window["__ScalaJSEnv"] = __ScalaJSEnv;
95+
| window["scalajsCom"] = global.scalajsCom;
96+
| } else {
97+
| throw error;
98+
| }
99+
| },
100+
| scripts: [${scriptsURIsAsJSStrings.mkString(", ")}]
101+
| });
102+
|})();
103+
|""".stripMargin
104+
}
105+
Seq(new MemVirtualJSFile("codeWithJSDOMContext.js").withContent(jsDOMCode))
106+
}
107+
108+
/** All the JS files that are passed to the VM.
109+
*
110+
* This method can overridden to provide custom behavior in subclasses.
111+
*
112+
* This method is overridden in `JSDOMNodeJSEnv` so that user-provided
113+
* JS files (excluding "init" files) are executed as *scripts* within the
114+
* jsdom environment, rather than being directly executed by the VM.
115+
*
116+
* The value returned by this method in `JSDOMNodeJSEnv` is
117+
* `initFiles() ++ customInitFiles() ++ codeWithJSDOMContext()`.
118+
*/
119+
override protected def getJSFiles(): Seq[VirtualJSFile] =
120+
initFiles() ++ customInitFiles() ++ codeWithJSDOMContext()
121+
122+
/** JS files to be loaded via scripts in the jsdom environment.
123+
*
124+
* This method can be overridden to provide a different list of scripts.
125+
*
126+
* The default value in `JSDOMNodeJSEnv` is `files`.
127+
*/
128+
protected def getScriptsJSFiles(): Seq[VirtualJSFile] =
129+
files
130+
131+
// Send code to Stdin
132+
override protected def sendVMStdin(out: OutputStream): Unit = {
133+
/* Do not factor this method out into AbstractNodeRunner or when mixin in
134+
* the traits it would use AbstractExtRunner.sendVMStdin due to
135+
* linearization order.
136+
*/
137+
sendJS(getJSFiles(), out)
138+
}
139+
}
140+
}
141+
142+
object JSDOMNodeJSEnv {
143+
final class Config private (
144+
val executable: String,
145+
val args: List[String],
146+
val env: Map[String, String]
147+
) {
148+
private def this() = {
149+
this(
150+
executable = "node",
151+
args = Nil,
152+
env = Map.empty
153+
)
154+
}
155+
156+
def withExecutable(executable: String): Config =
157+
copy(executable = executable)
158+
159+
def withArgs(args: List[String]): Config =
160+
copy(args = args)
161+
162+
def withEnv(env: Map[String, String]): Config =
163+
copy(env = env)
164+
165+
private def copy(
166+
executable: String = executable,
167+
args: List[String] = args,
168+
env: Map[String, String] = env
169+
): Config = {
170+
new Config(executable, args, env)
171+
}
172+
}
173+
174+
object Config {
175+
/** Returns a default configuration for a [[JSDOMNodeJSEnv]].
176+
*
177+
* The defaults are:
178+
*
179+
* - `executable`: `"node"`
180+
* - `args`: `Nil`
181+
* - `env`: `Map.empty`
182+
*/
183+
def apply(): Config = new Config()
184+
}
185+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.scalajs.jsenv.jsdomnodejs
2+
3+
import org.scalajs.jsenv.test._
4+
5+
import org.junit.Test
6+
import org.junit.Assert._
7+
8+
class JSDOMNodeJSEnvTest extends TimeoutComTests {
9+
10+
protected def newJSEnv: JSDOMNodeJSEnv = new JSDOMNodeJSEnv()
11+
12+
@Test
13+
def historyAPI: Unit = {
14+
"""|console.log(window.location.href);
15+
|window.history.pushState({}, "", "/foo");
16+
|console.log(window.location.href);
17+
""".stripMargin hasOutput
18+
"""|http://localhost/
19+
|http://localhost/foo
20+
|""".stripMargin
21+
}
22+
23+
}

project/build.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
sbt.version=0.13.15

project/plugins.sbt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.0.0-SNAPSHOT")
2+
3+
libraryDependencies += "org.scala-js" %% "scalajs-nodejs-env" % "1.0.0-SNAPSHOT"
4+
5+
unmanagedSourceDirectories in Compile +=
6+
baseDirectory.value.getParentFile / "jsdom-nodejs-env/src/main/scala"
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package testproject
2+
3+
import scala.scalajs.js
4+
5+
object Lib {
6+
7+
val document: js.Dynamic = js.Dynamic.global.document
8+
9+
def getElementsByTagName(name: String): js.Array[js.Dynamic] =
10+
document.getElementsByTagName(name).asInstanceOf[js.Array[js.Dynamic]]
11+
12+
/** appends a <p> with the message to the document */
13+
def appendDocument(msg: String): Unit = {
14+
val elem = document.createElement("p")
15+
elem.appendChild(document.createTextNode(msg))
16+
document.body.appendChild(elem)
17+
}
18+
19+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package testproject
2+
3+
object TestApp {
4+
5+
def main(args: Array[String]): Unit = {
6+
Lib.appendDocument("Hello World")
7+
Lib.appendDocument("Still Here!")
8+
9+
println(Lib.getElementsByTagName("p").head.innerHTML)
10+
}
11+
12+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package testproject
2+
3+
import scala.scalajs.js
4+
5+
import org.junit.Test
6+
import org.junit.Assert._
7+
8+
class LibTest {
9+
@Test def dummy_library_should_append_an_element(): Unit = {
10+
def count = Lib.getElementsByTagName("p").length
11+
12+
val oldCount = count
13+
Lib.appendDocument("foo")
14+
assertEquals(1, count - oldCount)
15+
}
16+
}

0 commit comments

Comments
 (0)