sbt and scala.js (with Node.js) can't run with a local .js dependency due to "TypeError: undefined is not a function"

sbt and scala.js (with Node.js) can't run with a local .js dependency due to "TypeError: undefined is not a function"

当我 运行 使用 sbt,scala.js,Node.js 上的 javascript 代码的本地位时,我需要帮助解决错误。

[info] Running net.walend.graph.results.PlotTime
Hello from scala
[error] /Users/dwalend/projects/ScalaGraphMinimizer/toGhPages/target/scala-2.11/toghpages-fastopt.js:1854
[error]   $g["hello"]();
[error]              ^
[error] TypeError: undefined is not a function
[error]     at $c_Lnet_walend_graph_results_PlotTime$.main__V (/Users/dwalend/projects/ScalaGraphMinimizer/toGhPages/target/scala-2.11/toghpages-fastopt.js:1854:14)
[error]     at $c_Lnet_walend_graph_results_PlotTime$.$$js$exported$meth$main__O (/Users/dwalend/projects/ScalaGraphMinimizer/toGhPages/target/scala-2.11/toghpages-fastopt.js:1861:8)
[error]     at $c_Lnet_walend_graph_results_PlotTime$.main (/Users/dwalend/projects/ScalaGraphMinimizer/toGhPages/target/scala-2.11/toghpages-fastopt.js:1864:15)
[error]     at Object.<anonymous> (/Users/dwalend/projects/ScalaGraphMinimizer/toGhPages/target/scala-2.11/toghpages-launcher.js:2:107)
[error]     at Module._compile (module.js:460:26)
[error]     at Object.Module._extensions..js (module.js:478:10)
[error]     at Module.load (module.js:355:32)
[error]     at Function.Module._load (module.js:310:12)
[error]     at Module.require (module.js:365:17)
[error]     at require (module.js:384:17)
org.scalajs.jsenv.ExternalJSEnv$NonZeroExitException: node.js exited with code 1
      at org.scalajs.jsenv.ExternalJSEnv$AbstractExtRunner.waitForVM(ExternalJSEnv.scala:96)
      at org.scalajs.jsenv.ExternalJSEnv$ExtRunner.run(ExternalJSEnv.scala:143)
      at org.scalajs.sbtplugin.ScalaJSPluginInternal$.org$scalajs$sbtplugin$ScalaJSPluginInternal$$jsRun(ScalaJSPluginInternal.scala:479)
      at org.scalajs.sbtplugin.ScalaJSPluginInternal$$anonfun$$anonfun$apply$$anonfun$apply.apply(ScalaJSPluginInternal.scala:539)
      at org.scalajs.sbtplugin.ScalaJSPluginInternal$$anonfun$$anonfun$apply$$anonfun$apply.apply(ScalaJSPluginInternal.scala:533)
      at scala.Function1$$anonfun$compose.apply(Function1.scala:47)

我最怀疑我的 build.sbt。 (它在一个没有 scala.js 的子项目中。)我想我有些不合时宜,但不知道要尝试哪些其他设置。

scalaVersion := "2.11.7"

scalacOptions ++= Seq("-unchecked", "-deprecation","-feature")

libraryDependencies ++= Seq(
  "org.scala-js" %%% "scalajs-dom" % "0.8.1"
)

//don't need phantomjs . //jsDependencies += RuntimeDOM

jsDependencies += "org.webjars" % "d3js" % "3.5.5-1" / "d3.min.js"

jsDependencies += ProvidedJS / "algorithmTime.js"

scalaJSStage in Global := FastOptStage

persistLauncher := true

我什至无法从 algorthmTime.js 和 Node.js 中得到 "hello"。

function hello() {
  console.log("hello from js")
}

Scala 中的 main() 非常漂亮 trim:

对象 PlotTime 扩展 js.JSApp {

def main(): Unit = {
  println("Hello from scala")
  global.hello()

  val png = global.dataToPng("benchmark/results/v0.1.2/dijkstra.csv")
  println(png)
}

}

在尝试 Node.js 之前,我进一步使用了 phantom.js 和 Rhino。 sbt 运行 进入我的本地 javascript 代码并在 d3 内部停顿

[info] Running net.walend.graph.results.PlotTime
Hello from scala
hello from js
org.mozilla.javascript.EcmaError: TypeError: Cannot call method "querySelector" of undefined (/Users/dwalend/.ivy2/cache/org.webjars/d3js/jars/d3js-3.5.5-1.jar#META-INF/resources/webjars/d3js/3.5.5/d3.min.js#3)
  at org.mozilla.javascript.ScriptRuntime.constructError(ScriptRuntime.java:3701)
  at org.mozilla.javascript.ScriptRuntime.constructError(ScriptRuntime.java:3679)
  at org.mozilla.javascript.ScriptRuntime.typeError(ScriptRuntime.java:3707)
  at org.mozilla.javascript.ScriptRuntime.typeError2(ScriptRuntime.java:3726)
  at org.mozilla.javascript.ScriptRuntime.undefCallError(ScriptRuntime.java:3743)
  at org.mozilla.javascript.ScriptRuntime.getPropFunctionAndThisHelper(ScriptRuntime.java:2269)
  at org.mozilla.javascript.ScriptRuntime.getPropFunctionAndThis(ScriptRuntime.java:2262)
  at org.mozilla.javascript.Interpreter.interpretLoop(Interpreter.java:1317)
  at org.mozilla.javascript.Interpreter.interpret(Interpreter.java:815)
  at org.mozilla.javascript.InterpretedFunction.call(InterpretedFunction.java:109)
  at org.mozilla.javascript.ContextFactory.doTopCall(ContextFactory.java:394)
  at org.mozilla.javascript.ScriptRuntime.doTopCall(ScriptRuntime.java:3102)
  at org.mozilla.javascript.InterpretedFunction.exec(InterpretedFunction.java:120)
  at org.mozilla.javascript.Context.evaluateString(Context.java:1078)
  at org.scalajs.jsenv.rhino.package$ContextOps$.evaluateFile$extension(package.scala:21)
  at org.scalajs.jsenv.rhino.RhinoJSEnv.org$scalajs$jsenv$rhino$RhinoJSEnv$$internalRunJS(RhinoJSEnv.scala:157)
  at org.scalajs.jsenv.rhino.RhinoJSEnv$Runner.run(RhinoJSEnv.scala:62)
  at org.scalajs.sbtplugin.ScalaJSPluginInternal$.org$scalajs$sbtplugin$ScalaJSPluginInternal$$jsRun(ScalaJSPluginInternal.scala:479)
  at org.scalajs.sbtplugin.ScalaJSPluginInternal$$anonfun$$anonfun$apply$$anonfun$apply.apply(ScalaJSPluginInternal.scala:539)
  at org.scalajs.sbtplugin.ScalaJSPluginInternal$$anonfun$$anonfun$apply$$anonfun$apply.apply(ScalaJSPluginInternal.scala:533)
  at scala.Function1$$anonfun$compose.apply(Function1.scala:47)
[trace] Stack trace suppressed: run last toGhPages/compile:run for the full output.
java.lang.RuntimeException: Exception while running JS code: TypeError: Cannot call method "querySelector" of undefined (/Users/dwalend/.ivy2/cache/org.webjars/d3js/jars/d3js-3.5.5-1.jar#META-INF/resources/webjars/d3js/3.5.5/d3.min.js#3)
  at scala.sys.package$.error(package.scala:27)
  at org.scalajs.jsenv.rhino.RhinoJSEnv.org$scalajs$jsenv$rhino$RhinoJSEnv$$internalRunJS(RhinoJSEnv.scala:173)
  at org.scalajs.jsenv.rhino.RhinoJSEnv$Runner.run(RhinoJSEnv.scala:62)
  at org.scalajs.sbtplugin.ScalaJSPluginInternal$.org$scalajs$sbtplugin$ScalaJSPluginInternal$$jsRun(ScalaJSPluginInternal.scala:479)
  at org.scalajs.sbtplugin.ScalaJSPluginInternal$$anonfun$$anonfun$apply$$anonfun$apply.apply(ScalaJSPluginInternal.scala:539)
  at org.scalajs.sbtplugin.ScalaJSPluginInternal$$anonfun$$anonfun$apply$$anonfun$apply.apply(ScalaJSPluginInternal.scala:533)
  at scala.Function1$$anonfun$compose.apply(Function1.scala:47)

这个错误表明我的代码正在做它应该做的事情。然而,互联网智慧说chasing in Rhino "querySelector" is a dead end and Node.js is a better choice

我怀疑我在系统中遗漏了一些 sbt 开关,但不知道还要寻找什么。

我也不明白它应该如何工作。我是 javascript 的新手,但我没有看到这些 javascript 文件中的任何一个如何依赖于任何生成的文件中的任何其他文件。 (scala.js's tutorial link 上的示例在 index.html 页面中使用脚本标签将所有内容组合在一起。)

> tree toGhPages/target/scala-2.11/
toGhPages/target/scala-2.11/
├── classes
│   ├── JS_DEPENDENCIES
│   ├── algorithmTime.js
│   └── net
│       └── walend
│           └── graph
│               └── results
│                   ├── PlotTime$.class
│                   ├── PlotTime$.sjsir
│                   └── PlotTime.class
├── toghpages-fastopt.js
├── toghpages-fastopt.js.map
└── toghpages-jsdeps.js

大局:我正在尝试使用 sbt、scala.js 和 d3 为 scala 图形算法库创建性能图表。第一张图表看起来很有希望,但 github 不支持 README.md 页面上的 javascript。为此,我需要一个简单的图像。我想更多地了解 scala.js 和 d3 这两个吸引我的方法。

快速修复

为了在 Node.js 中工作,请不要正确声明您希望可见的成员(即没有 var 或命名为 function):

hello = function() {
  console.log("hello from js")
};

这是一个 糟糕的 hack,但会解决 algorithmTime.js 的包含问题。 "Proper"解决在最后。

背景

编写不同的 Java 脚本文件通常很困难,因为没有标准化的方法。传统的 HTML-include 标签只有连接所有代码的语义。这是我们试图在 Scala.js 跑步者中模拟的语义。

然而,Node.js 使用 CommonJS 模块系统。在该系统中,库明确导出成员,使用站点将它们放入命名空间。这避免了命名冲突。

示例:

// Library (foo.js)
exports.foo = function() { return 1; };

// Using code
var lib = require("foo.js");
lib.foo() // returns 1

这允许库声明局部值而不会将它们泄露给调用者。 (注意:虽然我们这里有一个名为 require 的函数,但这是 而不是 RequireJS)。

然而,在 Scala.js 跑步者中,我们被期望 "just include" foo.js,这构成了挑战。我们应该为 require 调用的结果使用什么名称?这就是 commonJSName 的意思(参见下面的示例)。

如果未设置给定依赖项的 commonJSName,在 Node.js 运行程序中,我们将只发出

require(<name.js>);

没有将它分配给任何东西。 (为什么不直接转储你说的文件?再见合理的堆栈跟踪)。

这在 Node.js 中有一个非常有趣的效果。考虑以下文件 (bar.js):

var a = 1;
b = 2;

现在我们做:

require("bar.js")
console.log(a); // undefined
console.log(b); // 2

似乎 b 泄漏到全局上下文中,而 a 没有。这就是 quickfix 起作用的原因。

解决方案

为了更好的解决方案,您有两个选择:

  1. 致力于 Node.js,编写特定于其模块系统的库
  2. 自动检测你所在的环境并动态适应(许多 JS 库都这样做)

解决方案 1

modules.exports = function() {
  console.log("hello from js")
};

commonJSName 添加到您的依赖项中:

jsDependencies += ProvidedJS / "algorithmTime.js" commonJSName "hello"

除 Node.js 外,这将导致失败,原因有二:

  1. JS VM 可能不支持 CommonJS 样式包括
  2. 像这样覆盖完整的 exports 命名空间不是标准的 CommonJS,而是特定于 Node.js (IIRC)。

解决方案 2

自动检测:

var hello = {};

// Scope to prevent leakage
(function(exp) {
  hello.hello = function() {
    console.log("hello from js");
  }
})(exports ? exports : hello);

在这种情况下,您还需要设置 commonJSName

此外,您可能已经从代码中怀疑,这需要您有一个额外的间接寻址,因为 CommonJS 要求顶级导出是一个对象 (IIRC)。因此,您需要调整 Scala.js 代码:

global.hello.hello();

但是,如果您的库导出多个符号,无论如何这可能是个好主意。此外,这可能适用于大多数 JS 环境(并且应该适用于我们提供的三种环境 Scala.js)。

结语

我们(Scala.js 团队)对这种情况非常不满,因为我们认为包含 JS 库应该和依赖其他 Scala and/or Java 库一样容易JVM 土地。但是,我们还没有找到更好的解决方案来解决这种不支持 every 包含样式的问题,这是一项巨大的设计、工程和维护工作(如果系统更改或新系统出现怎么办?起?)。

相关讨论:#457 and #706.