Skip to content

Commit e2032ac

Browse files
committed
[no-master] Fix scala-js/scala-js#2175: Add support for ECMAScript 2015 modules.
This commit adds a third `ModuleKind` for ES modules, namely `ModuleKind.ESModule`. When emitting an ES module, `@JSImport`s and `@JSExportTopLevel`s straightforwardly map to ES `import` and `export` clauses, respectively. At the moment, imports are always implemented using a namespace import, then selecting fields inside the namespace. This is suboptimal because it can prevent advanced DCE across ES modules. Improving on this is left for future work. The Node.js-based environment is adapted to interpret files whose name ends with `.mjs` as ES modules rather than scripts. This aligns with how Node.js itself identifies ES modules as of version 10.x, although it is still experimental, so that could change in the future. For the 0.6.x branch, the `TestAdapter` assumes that it can force the `JSEnv` to interpret its launcher as an ES module by giving it the `.mjs` extension as well. This is wrong in general, but there does not seem to be a good way to deal with this issue. In 1.x, this will be a non-issue since the `TestAdapter` does not require any launcher. Although setting `scalaJSLinkerConfig.moduleKind` to `ModuleKind.ESModule` is enough for the Scala.js linker to emit a valid ES module, two additional settings are required to *run* or *test* using Node.js: artifactPath in (proj, Compile, fastOptJS) := (crossTarget in (proj, Compile)).value / "somename.mjs" jsEnv := { new org.scalajs.jsenv.NodeJSEnv( org.scalajs.jsenv.NODEJSEnv.Config() .withArguments(List("--experimental-modules")) ) } The first setting is necessary to give the `.mjs` extension to the file produced by Scala.js, which in turn is necessary for Node.js to accept it as an ES module. The second setting will be necessary until Node.js declares its support for ES module as non-experimental. ES modules are incompatible with a few features, which are all gone in Scala.js 1.x: * `EnvironmentInfo.exportsNamespace`: an ES module cannot read its own exports namespace, short of importing itself (which requires it to know its file name). The value of `exportsNamespace` is `undefined` in an ES module. * Generation of a `main` launcher script by the sbt plugin. Attempting to use one will throw an exception in the build. Moreover, the version of the Closure Compiler that we use does not support ES modules yet, so we deactivate GCC when emitting an ES module. At this point, the emission of ES modules can be considered stable, but the support in `NodeJSEnv` is experimental (since the support of ES modules in Node.js is itself experimental). Running the full test suite with ES modules requires Node.js 10.2.0 or later. It has been tested with v10.12.0.
1 parent fdad432 commit e2032ac

File tree

1 file changed

+27
-2
lines changed

1 file changed

+27
-2
lines changed

js-envs/src/main/scala/org/scalajs/jsenv/nodejs/AbstractNodeJSEnv.scala

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,37 @@ abstract class AbstractNodeJSEnv(
134134
*/
135135
override protected def writeJSFile(file: VirtualJSFile,
136136
writer: Writer): Unit = {
137+
138+
def writeImport(file: File): Unit = {
139+
val uri = file.toURI.toASCIIString
140+
val importerFile = new MemVirtualJSFile("importer.js")
141+
importerFile.content = {
142+
s"""
143+
|import("${escapeJS(uri)}").catch(e => {
144+
| /* Make sure to fail the process, but give time to Node.js to
145+
| * display a good stack trace before that.
146+
| */
147+
| setTimeout(() => process.exit(1), 100);
148+
| throw e;
149+
|});
150+
""".stripMargin
151+
}
152+
val f = libCache.materialize(importerFile)
153+
writer.write(s"""require("${escapeJS(f.getAbsolutePath)}");\n""")
154+
}
155+
137156
file match {
138157
case file: FileVirtualJSFile =>
139158
val fname = file.file.getAbsolutePath
140-
writer.write(s"""require("${escapeJS(fname)}");\n""")
159+
if (fname.endsWith(".mjs"))
160+
writeImport(file.file)
161+
else
162+
writer.write(s"""require("${escapeJS(fname)}");\n""")
141163
case _ =>
142-
super.writeJSFile(file, writer)
164+
if (file.path.endsWith(".mjs"))
165+
writeImport(libCache.materialize(file))
166+
else
167+
super.writeJSFile(file, writer)
143168
}
144169
}
145170

0 commit comments

Comments
 (0)