本文概述
如果使用Scala语言到JavaScript的编译器Scala.js, 你可能会发现Scala.js的标准依赖项管理在现代JavaScript世界中过于严格。 Scala.js使用WebJars管理依赖关系, 而JavaScript开发人员使用NPM管理依赖关系。由于NPM产生的依赖关系是服务器端的, 因此通常需要使用Browserify或Webpack生成浏览器代码的附加步骤。
在本文中, 我将介绍如何将Scala.js与NPM上可用的大量JavaScript模块集成在一起。你可以在此GitHub存储库中查看此处描述的技术的有效示例。使用本示例并阅读本文, 你将能够使用NPM收集JavaScript库, 使用Browserify创建捆绑软件, 并将结果用于你自己的Scala.js项目。所有这些甚至都没有安装Node.js, 因为所有内容都由SBT管理。
Browserify的魔力也可以在Java世界中发挥出色!
鸣叫
管理Scala.js的依赖项
如今, 用可编译为JavaScript的语言编写应用程序已成为一种非常普遍的做法。越来越多的人开始使用扩展的JavaScript语言(例如CoffeeScript或TypeScript)或转译器(例如Babel), 现在可以使用它们来使用ES6。同时, Google Web Toolkit(从Java到JavaScript的编译器)主要用于企业应用程序。由于这些原因, 作为Scala开发人员, 我不再考虑使用Scala.js成为一个奇怪的选择。编译器速度很快, 生成的代码高效, 并且总体而言, 这只是在前端和后端使用相同语言的一种方式。
也就是说, 在JavaScript世界中使用Scala工具还不是百分百自然的。有时你必须填补从JavaScript生态系统到Scala生态系统的空白。 Scala.js作为一种语言与JavaScript具有出色的互操作性。因为Scala.js是将Scala语言转换为JavaScript的编译器, 所以将Scala代码与现有的JavaScript代码接口非常容易。最重要的是, Scala.js使你能够创建类型化的接口(或立面)来访问未类型化的JavaScript库(类似于对TypeScript的操作)。对于习惯于使用强类型语言(例如Java, Scala甚至是Haskell)的开发人员, JavaScript的类型过于宽松。如果你是这样的开发人员, 可能的主要原因可能是因为你可能想使用Scala.js是在无类型语言的基础上获得一种(强烈)类型的语言。
基于SBT并仍然有些开放的标准Scala.js工具链中的问题是:如何在项目中包含依赖项(如其他JavaScript库)? SBT在WebJars上标准化, 因此应该使用WebJars来管理依赖项。不幸的是, 以我的经验证明这是不够的。
WebJars的问题
如前所述, 用于检索JavaScript依赖关系的标准Scala.js方法是基于WebJars的。毕竟, Scala是一种JVM语言。 Scala.js主要使用SBT来构建程序, 最后SBT在管理JAR依赖项方面非常出色。
因此, 为将Java依赖项导入JVM世界中而精确定义了WebJar格式。 WebJar是包含Web资产的JAR文件, 其中简单的JAR文件仅包含编译的Java类。因此, Scala.js的想法是你应该通过简单地添加WebJar依赖关系来导入JavaScript依赖关系, 这与Scala添加JAR依赖关系的方式类似。
好主意, 除非它不起作用
Webjars的最大问题是, 随机JavaScript库上的任意版本很少可以作为WebJar使用。同时, 绝大多数JavaScript库都可以作为NPM模块使用。但是, 使用自动的npm-to-webjar打包程序在NPM和WebJars之间建立起了桥梁。因此, 我尝试导入一个库, 该库可以作为NPM模块使用。就我而言, 它是VoxelJS, 一个用于在网页中构建类似于Minecraft的世界的库。我试图以WebJar的形式请求该库, 但是桥接失败仅是因为描述符中没有许可证字段。
你可能还会由于其他原因而遇到这种令人沮丧的体验。简而言之, 你似乎无法像WebJar那样随意访问每个库。你必须使用WebJars来访问JavaScript库的要求似乎太严格了。
输入NPM和Browserify
正如我已经指出的那样, 大多数JavaScript库的标准打包格式是Node.js的任何版本中都包含的Node Package Manager(NPM)。通过使用NPM, 你可以轻松访问几乎所有可用的JavaScript库。
请注意, NPM是节点程序包管理器。 Node是V8 JavaScript引擎的服务器端实现, 它安装Node.js在服务器端使用的软件包。照原样, NPM对浏览器没有用。但是, 由于普遍使用了Browserify工具, 因此NPM的功能已经扩展了一段时间, 可以与浏览器应用程序一起使用, 该工具当然也作为NPM软件包分发。
简而言之, Browserify是浏览器的打包程序。它将收集NPM模块, 生成可在浏览器应用程序中使用的”捆绑包”。许多JavaScript开发人员都是以这种方式工作的-他们使用NPM管理软件包, 然后通过Browserify使其在Web应用程序中使用。请注意, 还有其他以相同方式工作的工具, 例如Webpack。
填补SBT与NPM之间的差距
出于我刚才描述的原因, 我想要的是一种使用NPM从Web安装依赖项, 调用Browserify收集浏览器依赖项, 然后将其与Scala.js结合使用的方法。结果任务比我预期的要复杂一些, 但仍然可以实现。确实, 我做了这项工作, 并在这里进行描述。
为简单起见, 我之所以选择Browserify也是因为我发现可以在SBT中运行它。我没有尝试过Webpack, 尽管我猜也有可能。幸运的是, 我不必白手起家。已经有很多片段:
- SBT已经支持NPM。为Play框架开发的sbt-web插件可以安装NPM依赖项。
- SBT支持JavaScript的执行。借助sbt-jsengine插件, 你可以执行Node工具而无需安装Node本身。
- Scala.js可以使用生成的包。在Scala.js中, 有一个串联函数可在你的应用程序中包含任意JavaScript库。
使用这些功能, 我创建了一个SBT任务, 该任务可以下载NPM依赖项, 然后调用Browserify, 生成bundle.js文件。我尝试将过程集成到编译链中, 并且可以自动运行整个过程, 但是必须在每次编译时都要处理捆绑程序, 这太慢了。另外, 你不会一直更改依赖关系;因此, 当你更改依赖关系时, 必须一次手动创建一个捆绑软件是合理的。
因此, 我的解决方案是建立一个子项目。该子项目使用NPM和Browserify下载并打包JavaScript库。然后, 我添加了bundle命令来执行依赖项的收集。生成的捆绑软件将添加到Scala.js应用程序中要使用的资源中。
每当你更改JavaScript依赖项时, 都应该手动执行此”捆绑包”。如前所述, 在编译链中它不是自动化的。
如何使用捆扎机
如果要使用我的示例, 请执行以下操作:首先, 使用常规的Git命令检出存储库。
git clone https://github.com/sciabarra/scalajs-browserify/
然后, 在你的Scala.js项目中复制bundle文件夹。这是一个用于捆绑的子项目。要连接到主项目, 应在build.sbt文件中添加以下行:
val bundle = project.in(file("bundle"))
jsDependencies += ProvidedJS / "bundle.js"
addCommandAlias("bundle", "bundle/bundle")
另外, 你必须将以下行添加到project / plugins.sbt文件:
addSbtPlugin("com.typesafe.sbt" % "sbt-web" % "1.1.1")
addSbtPlugin("com.typesafe.sbt" % "sbt-js-engine" % "1.1.3")
完成后, 你将拥有一个新的命令包, 可用于收集依赖项。它将在src / main / resources文件夹下生成一个文件bundle.js。
该捆绑包如何包含在你的Scala.js应用程序中?
刚刚描述的bundle命令收集与NPM的依赖关系, 然后创建bundle.js。当你运行fastOptJS或fullOptJS命令时, ScalaJS将创建一个myproject-jsdeps.js, 包括你指定为JavaScript依赖项的所有资源, 以及你的bundle.js。为了在你的应用程序中包含捆绑的依赖项, 你应该使用以下包含项:
<script src="target/scala-2.11/myproject-jsdeps.js"></script>
<script src="target/scala-2.11/myproject-fastopt.js"></script>
<script src="target/scala-2.11/myproject-launcher.js"></script>
你的捆绑包现在可以作为myproject-jsdeps.js的一部分使用。该捆绑包已准备就绪, 我们已经完成了一些任务(导入依赖项并将其导出到浏览器)。下一步是使用JavaScript库, 这是一个不同的问题, 即Scala.js编码问题。为了完整起见, 我们现在将讨论如何在Scala.js中使用捆绑软件, 并创建外观以使用我们导入的库。
使用Scala.js, 你可以轻松地在服务器和客户端之间共享代码
鸣叫
在Scala.js应用程序中使用通用JavaScript库
概括地说, 我们已经看到了如何使用NPM和Browserify创建捆绑包, 并将该捆绑包包含在Scala.js中。但是, 我们如何使用通用JavaScript库?
我们将在本文的其余部分中详细解释的完整过程是:
- 从NPM中选择你的库, 并将它们包含在bundle / package.json中。
- 使用require将它们加载到bundle / lib.js中的库模块文件中。
- 编写Scala.js外观, 以解释Scala.js中的Bundle对象。
- 最后, 使用新类型的库对应用程序进行编码。
添加依赖
使用NPM, 你必须将依赖项包含在标准的package.json文件中。
因此, 假设你要使用两个著名的库, 例如jQuery和Loadash。该示例仅用于演示目的, 因为已经有一个出色的jQuery包装器可作为具有适当模块的Scala.js的依赖项, 而Lodash在Scala世界中毫无用处。尽管如此, 我认为这是一个很好的例子, 但仅作为例子。
因此, 请访问npmjs.com网站并找到你要使用的库, 并选择一个版本。假设你选择的是jquery-browserify版本13.0.0和lodash版本4.3.0。然后, 如下更新你的packages.json的依赖关系块:
"dependencies": {
"browserify": "13.0.0", "jquery-browserify": "1.8.1", "lodash": "4.3.0"
}
始终保持browserify, 以生成捆绑软件。但是, 你无需将其包括在捆绑软件中。
请注意, 如果已安装NPM, 则可以从bundle目录中键入:
npm install --save jquery-browserify lodash
它还将更新package.json。如果你没有安装NPM, 请不要担心。 SBT将安装Node.js和NPM的Java版本, 下载所需的JAR并运行它们。当你从SBT运行bundle命令时, 所有这些都将得到管理。
导出图书馆
现在我们知道了如何下载软件包。下一步是指示Browserify将它们打包在一起, 并使它们可用于应用程序的其余部分。
Browserify是require的收集器, 它模拟浏览器的Node.js行为, 这意味着你需要在某处需要require导入库。因为我们需要将这些库导出到Scala.js, 所以该捆绑包还会生成一个名为Bundle的顶级JavaScript对象。因此, 你需要做的是编辑lib.js, 这将导出一个JavaScript对象, 并需要所有库作为该对象的字段。
如果我们要导出到Scala.js jQuery和Lodash库, 则在代码中表示:
module.exports = {
"jquery": require("jquery-browserify"), "lodash": require("lodash")
}
现在, 只需执行命令捆绑包, 库就会被下载, 收集并放置在捆绑包中, 准备在你的Scala.js应用程序中使用。
访问捆绑
至今:
- 你已将捆绑包子项目安装在Scala.js项目中, 并对其进行了正确配置。
- 对于你想要的任何库, 你都将其添加到package.json中。
- 你在lib.js中需要它们。
- 你执行了bundle命令。
结果, 你现在有了一个Bundle顶级JavaScript对象, 提供了库的所有入口点, 可用作该对象的字段。
现在你可以将其与Scala.js一起使用。在最简单的情况下, 你可以执行以下操作来访问库:
@js.native
object Bundle extends js.Object {
def jquery : js.Any = js.native
def lodash: js.Any = js.native
}
此代码使你可以从Scala.js访问库。但是, 这不是使用Scala.js的方法, 因为这些库仍未键入。相反, 你应该编写类型化的”外观”或包装, 以便可以按类型化的扩展方式使用最初未类型化的JavaScript库。
我不能在这里告诉你如何编写Facades, 因为它取决于你要在Scala.js中包装的特定JavaScript库。我将仅显示一个示例, 以完成讨论。请查看官方的Scala.js文档以获取更多详细信息。另外, 你可以查阅可用外墙的列表并阅读源代码以获取灵感。
到目前为止, 我们已经介绍了仍未映射的任意库的过程。在本文的其余部分中, 我指的是具有已经可用的外观的库, 因此该讨论仅是示例。
在Scala.js中包装JavaScript API
当在JavaScript中使用require时, 你会得到一个对象, 该对象可以有很多不同的东西。它可以是函数或对象。它甚至可以只是字符串或布尔值, 在这种情况下, 仅出于副作用才调用require。
在我正在执行的示例中, jquery是一个函数, 返回一个提供其他方法的对象。典型用法是jquery(<selector>)。<method>。这是一种简化, 因为jquery还允许使用$。<method>, 但是在此简化示例中, 我将不涉及所有这些情况。通常请注意, 对于复杂的JavaScript库, 并非所有API都可以轻松映射到静态Scala类型。你可能需要求助于js.Dynamic, 它为JavaScript对象提供了动态(无类型)接口。
因此, 为了捕获更常见的用例, 我在Bundle对象jquery中定义了:
def jquery : js.Function1[js.Any, Jquery] = js.native
该函数将返回一个jQuery对象。在我的情况下, 特征的实例是用一种方法定义的(为简单起见, 你可以添加自己的方法):
@js.native
trait Jquery extends js.Object {
def text(arg: js.Any): Jquery = js.native
}
对于Lodash库, 我们将整个库建模为JavaScript对象, 因为它是可以直接调用的函数的集合:
def lodash: Lodash = js.native
lodash特征如下所示(也是一种简化, 你可以在此处添加方法):
@js.native
trait Lodash extends js.Object {
def camelCase(arg: js.Any): String = js.native
}
使用这些定义, 我们现在终于可以使用底层的jQuery和Lodash库编写Scala代码了, 它们都从NPM加载, 然后浏览:
object Main extends JSApp {
def main(): Unit = {
import Bundle._
jquery("#title").text(lodash.camelCase("This is a test"))
}
}
你可以在此处查看完整的示例。
总结
我是Scala开发人员, 当我发现Scala.js时我很兴奋, 因为我可以为服务器和客户端使用相同的语言。由于Scala与Java相比更类似于JavaScript, 因此Scala.js在浏览器中非常简单自然。此外, 你还可以将Scala的强大功能用作丰富的库, 宏, 强大的IDE和构建工具的集合。其他关键优势是你可以在服务器和客户端之间共享代码。在很多情况下, 此功能很有用。如果你为Javascript使用Transpiler(例如Coffeescript, Babel或Typescript), 则在使用Scala.js时不会注意到太多差异, 但是仍然有很多优点。秘诀是充分利用每个世界的优势, 并确保它们之间的良好协作。
相关:为什么我应该学习Scala?