From 130eecdecf42c87030134c422ad7f53c0be41cd5 Mon Sep 17 00:00:00 2001 From: Domantas Petrauskas Date: Wed, 4 May 2022 22:06:48 +0300 Subject: [PATCH] Enable scalafmt --- .github/workflows/ci.yml | 2 + .scalafmt.conf | 25 ++ build.sbt | 79 ++-- project/plugins.sbt | 11 +- .../scala/scalajsbundler/BundlerFile.scala | 203 ++++----- .../scalajsbundler/ExternalCommand.scala | 96 ++-- .../scala/scalajsbundler/NpmPackage.scala | 6 +- .../scala/scalajsbundler/PackageJson.scala | 89 ++-- .../src/main/scala/scalajsbundler/Stats.scala | 87 ++-- .../main/scala/scalajsbundler/Webpack.scala | 242 +++++----- .../scalajsbundler/WebpackDevServer.scala | 67 +-- .../scalajsbundler/WebpackEntryPoint.scala | 14 +- .../sbtplugin/LibraryTasks.scala | 96 ++-- .../sbtplugin/NpmUpdateTasks.scala | 120 ++--- .../sbtplugin/PackageJsonTasks.scala | 55 ++- .../sbtplugin/SBTBundlerFile.scala | 43 +- .../sbtplugin/ScalaJSBundlerPlugin.scala | 415 ++++++++---------- .../scalajsbundler/sbtplugin/Settings.scala | 212 +++++---- .../sbtplugin/WebpackTasks.scala | 36 +- .../util/CachedBundleFiles.scala | 1 + .../scala/scalajsbundler/util/Commands.scala | 6 +- .../main/scala/scalajsbundler/util/JS.scala | 6 +- .../scala/scalajsbundler/util/JSBundler.scala | 56 ++- .../scalajsbundler/util/JSPrinters.scala | 16 +- .../scala/scalajsbundler/util/JSTrees.scala | 176 ++++---- .../util/ScalaJSNativeLibraries.scala | 40 +- .../scalajsbundler/sbtplugin/NpmAssets.scala | 14 +- .../sbtplugin/WebScalaJSBundlerPlugin.scala | 35 +- 28 files changed, 1172 insertions(+), 1076 deletions(-) create mode 100644 .scalafmt.conf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ffed4906..e2b8f7e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,8 @@ jobs: with: java-version: "adopt@1.11" - uses: coursier/cache-action@v5 + - name: Scalafmt + run: sbt scalafmtSbtCheck scalafmtCheckAll - name: Unit tests run: sbt test - name: Scripted tests diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 00000000..851c368f --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,25 @@ +version = 3.5.2 +runner.dialect = scala212 +maxColumn = 120 + +danglingParentheses.callSite = false +danglingParentheses.ctrlSite = false +danglingParentheses.defnSite = false +docstrings.oneline = fold +docstrings.style = SpaceAsterisk +literals.hexDigits = Upper +literals.scientific = Upper +newlines.afterCurlyLambdaParams = never +newlines.alwaysBeforeElseAfterCurlyIf = false +newlines.beforeCurlyLambdaParams = never +newlines.topLevelStatements = [before] +optIn.forceBlankLineBeforeDocstring = true + +rewrite.sortModifiers.order = [ + "override", "final", "implicit", + "sealed", "abstract", + "private", "protected", + "open", "opaque", "infix", + "transparent", "inline", + "lazy" +] \ No newline at end of file diff --git a/build.sbt b/build.sbt index 8c46c871..ed37fc62 100644 --- a/build.sbt +++ b/build.sbt @@ -1,14 +1,16 @@ val scalaJSVersion = sys.env.getOrElse("SCALAJS_VERSION", "1.3.0") lazy val `scalajs-bundler-linker` = - project.in(file("scalajs-bundler-linker")) + project + .in(file("scalajs-bundler-linker")) .settings( scalaVersion := "2.12.11", libraryDependencies += "org.scala-js" %% "scalajs-linker" % scalaJSVersion ) val `sbt-scalajs-bundler` = - project.in(file("sbt-scalajs-bundler")) + project + .in(file("sbt-scalajs-bundler")) .enablePlugins(SbtPlugin, BuildInfoPlugin) .settings(commonSettings) .settings( @@ -23,11 +25,12 @@ val `sbt-scalajs-bundler` = val () = scriptedDependencies.value val () = publishLocal.value val () = (publishLocal in `scalajs-bundler-linker`).value - }, + } ) val `sbt-web-scalajs-bundler` = - project.in(file("sbt-web-scalajs-bundler")) + project + .in(file("sbt-web-scalajs-bundler")) .enablePlugins(SbtPlugin) .settings(commonSettings) .settings( @@ -49,14 +52,17 @@ val `sbt-web-scalajs-bundler` = // plugins. I can not do that in the `doc` project below because the // scalaVersion is not compatible. val apiDoc = - project.in(file("api-doc")) + project + .in(file("api-doc")) .enablePlugins(ScalaUnidocPlugin) .settings(noPublishSettings: _*) .settings( scalacOptions in (ScalaUnidoc, unidoc) ++= Seq( "-groups", - "-doc-source-url", s"https://github.com/scalacenter/scalajs-bundler/blob/v${version.value}€{FILE_PATH}.scala", - "-sourcepath", (baseDirectory in ThisBuild).value.absolutePath + "-doc-source-url", + s"https://github.com/scalacenter/scalajs-bundler/blob/v${version.value}€{FILE_PATH}.scala", + "-sourcepath", + (baseDirectory in ThisBuild).value.absolutePath ), unidocProjectFilter in (ScalaUnidoc, unidoc) := inAnyProject -- inProjects(`scalajs-bundler-linker`) ) @@ -65,7 +71,8 @@ val apiDoc = val ornateTarget = Def.setting(target.value / "ornate") val manual = - project.in(file("manual")) + project + .in(file("manual")) .enablePlugins(OrnatePlugin) .settings(noPublishSettings: _*) .settings( @@ -85,34 +92,38 @@ val manual = ) val `scalajs-bundler` = - project.in(file(".")) + project + .in(file(".")) .settings(noPublishSettings: _*) .aggregate(`sbt-scalajs-bundler`, `sbt-web-scalajs-bundler`) -inThisBuild(List( - scalacOptions ++= Seq( - "-feature", - "-deprecation", - "-encoding", "UTF-8", - "-unchecked", - "-Xlint", - "-Yno-adapted-args", - "-Ywarn-dead-code", - "-Ywarn-numeric-widen", - "-Ywarn-value-discard", - "-Xfuture" - ), - scmInfo := Some( - ScmInfo( - url("https://github.com/scalacenter/scalajs-bundler"), - "scm:git@github.com:scalacenter/scalajs-bundler.git" - ) - ), - organization := "ch.epfl.scala", - homepage := Some(url(s"https://github.com/scalacenter/scalajs-bundler")), - licenses := Seq("MIT License" -> url("http://opensource.org/licenses/mit-license.php")), - developers := List(Developer("julienrf", "Julien Richard-Foy", "julien.richard-foy@epfl.ch", url("http://julien.richard-foy.fr"))) -)) +inThisBuild( + List( + scalacOptions ++= Seq( + "-feature", + "-deprecation", + "-encoding", + "UTF-8", + "-unchecked", + "-Xlint", + "-Yno-adapted-args", + "-Ywarn-dead-code", + "-Ywarn-numeric-widen", + "-Ywarn-value-discard", + "-Xfuture" + ), + scmInfo := Some( + ScmInfo( + url("https://github.com/scalacenter/scalajs-bundler"), + "scm:git@github.com:scalacenter/scalajs-bundler.git" + ) + ), + organization := "ch.epfl.scala", + homepage := Some(url(s"https://github.com/scalacenter/scalajs-bundler")), + licenses := Seq("MIT License" -> url("http://opensource.org/licenses/mit-license.php")), + developers := List( + Developer("julienrf", "Julien Richard-Foy", "julien.richard-foy@epfl.ch", url("http://julien.richard-foy.fr"))) + )) lazy val commonSettings = List( scriptedLaunchOpts ++= Seq( @@ -122,7 +133,7 @@ lazy val commonSettings = List( ), scriptedBufferLog := false, crossSbtVersions := List("1.2.8"), - sbtVersion in pluginCrossBuild := "1.2.8", + sbtVersion in pluginCrossBuild := "1.2.8" ) lazy val noPublishSettings = diff --git a/project/plugins.sbt b/project/plugins.sbt index aa94ac99..5727cf7a 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,8 @@ -addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") -addSbtPlugin("com.novocode" % "sbt-ornate" % "0.6") -addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.3") -addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.10.0") -addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.6") +addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") +addSbtPlugin("com.novocode" % "sbt-ornate" % "0.6") +addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.3") +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.10.0") +addSbtPlugin("com.geirsson" % "sbt-ci-release" % "1.5.6") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") ivyLoggingLevel in ThisBuild := UpdateLogging.Quiet diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/BundlerFile.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/BundlerFile.scala index a2d96a94..f94c60c8 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/BundlerFile.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/BundlerFile.scala @@ -7,77 +7,74 @@ import scalajsbundler.Stats.WebpackStats import scalajsbundler.util.CachedBundleFiles import scala.collection.immutable.ListSet -/** - * Files used in the `ScalaJSBundler` pipeline. - */ +/** Files used in the `ScalaJSBundler` pipeline. */ sealed trait BundlerFile extends Product with Serializable { def file: File } object BundlerFile { - /** - * Files that may be inputs to the webpack process - */ + + /** Files that may be inputs to the webpack process */ sealed trait WebpackInput extends BundlerFile { def project: String } - /** - * Internal-only files - */ + /** Internal-only files */ sealed abstract class Internal extends BundlerFile - /** - * A library-mode entrypoint file - * @param application The [[Application]] this entrypoint was generated from - * @param file The file containing the entry point + /** A library-mode entrypoint file + * @param application + * The [[Application]] this entrypoint was generated from + * @param file + * The file containing the entry point */ - case class EntryPoint(application: Application, file: java.io.File) - extends Internal - with WebpackInput { + case class EntryPoint(application: Application, file: java.io.File) extends Internal with WebpackInput { def project: String = application.project } object EntryPoint { + /** Filename of the generated bundle, given its module entry name */ def fileName(entry: String): String = s"$entry-entrypoint.js" } - /** - * The package.json file, used for populating the node_modules folder + /** The package.json file, used for populating the node_modules folder * - * @param file The file reference for the package.json + * @param file + * The file reference for the package.json */ case class PackageJson(file: java.io.File) extends Internal - /** - * A webpack configuration file. + /** A webpack configuration file. * - * @param application The [[Application]] this file runs webpack for - * @param file The webpack.config.js file reference + * @param application + * The [[Application]] this file runs webpack for + * @param file + * The webpack.config.js file reference */ - case class WebpackConfig(application: Application, file: java.io.File) - extends Internal { + case class WebpackConfig(application: Application, file: java.io.File) extends Internal { def project: String = application.project def targetDir: Path = file.getParentFile.toPath - /** - * Returns the Library identifying the asset produced by scala.js through webpack stats - */ + /** Returns the Library identifying the asset produced by scala.js through webpack stats */ def asLibrary(stats: Option[WebpackStats]): Library = - Library(project, - stats.flatMap { s => - s.resolveAsset(targetDir, project) - }.getOrElse(targetDir.resolve(Library.fileName(project)).toFile), - stats.map { s => - s.resolveAllAssets(targetDir) - }.getOrElse(Nil) - ) - - /** - * Returns library from a set of cached files - * By convention the first element is the `file` and the rest are the assets + Library( + project, + stats + .flatMap { s => + s.resolveAsset(targetDir, project) + } + .getOrElse(targetDir.resolve(Library.fileName(project)).toFile), + stats + .map { s => + s.resolveAllAssets(targetDir) + } + .getOrElse(Nil) + ) + + /** Returns library from a set of cached files By convention the first element is the `file` and the rest are the + * assets */ def asLibraryFromCached(cached: Set[File]): Library = { assert(cached.size >= 1) @@ -85,21 +82,25 @@ object BundlerFile { Library(project, cached.head, assets) } - /** - * Returns the Application for this configuration identifying the asset produced by scala.js through webpack stats + /** Returns the Application for this configuration identifying the asset produced by scala.js through webpack stats */ def asApplicationBundle(stats: Option[WebpackStats]): ApplicationBundle = - ApplicationBundle(project, - stats.flatMap { s => - s.resolveAsset(targetDir, project) - }.getOrElse(targetDir.resolve(ApplicationBundle.fileName(project)).toFile), - stats.map { s => - s.resolveAllAssets(targetDir) - }.getOrElse(Nil)) - - /** - * Returns an application bundle from a set of cached files - * By convention the first element is the `file` and the rest are the assets + ApplicationBundle( + project, + stats + .flatMap { s => + s.resolveAsset(targetDir, project) + } + .getOrElse(targetDir.resolve(ApplicationBundle.fileName(project)).toFile), + stats + .map { s => + s.resolveAllAssets(targetDir) + } + .getOrElse(Nil) + ) + + /** Returns an application bundle from a set of cached files By convention the first element is the `file` and the + * rest are the assets */ def asApplicationBundleFromCached(cached: Set[File]): ApplicationBundle = { assert(cached.size >= 1) @@ -108,9 +109,7 @@ object BundlerFile { } } - /** - * Public webpack artifacts -- those that might be served to clients or packaged - */ + /** Public webpack artifacts -- those that might be served to clients or packaged */ sealed abstract class Public extends BundlerFile { def project: String // Attributed files, the first is the main file and the rest are assets @@ -118,42 +117,44 @@ object BundlerFile { def `type`: BundlerFileType } - /** - * The Scala.js application itself, aka -fastopt.js or -opt.js + /** The Scala.js application itself, aka -fastopt.js or -opt.js * - * @param project The application project name - * @param file The file containing the application javascript - * @param assets All the assets on the application + * @param project + * The application project name + * @param file + * The file containing the application javascript + * @param assets + * All the assets on the application */ - case class Application(project: String, file: File, assets: List[java.io.File]) - extends Public - with WebpackInput { + case class Application(project: String, file: File, assets: List[java.io.File]) extends Public with WebpackInput { def targetDir: Path = file.getParentFile.toPath val `type`: BundlerFileType = BundlerFileType.Application + def asLoader: Loader = - Loader(this, - targetDir - .resolve(Loader.fileName(project)) - .toFile) + Loader( + this, + targetDir + .resolve(Loader.fileName(project)) + .toFile) def asEntryPoint: EntryPoint = - EntryPoint(this, - targetDir - .resolve(EntryPoint.fileName(project)) - .toFile) + EntryPoint( + this, + targetDir + .resolve(EntryPoint.fileName(project)) + .toFile) def asApplicationBundle: ApplicationBundle = - ApplicationBundle(project, - targetDir - .resolve(ApplicationBundle.fileName(project)) - .toFile, - assets) - - /** - * Returns an application bundle from a set of cached files - */ + ApplicationBundle( + project, + targetDir + .resolve(ApplicationBundle.fileName(project)) + .toFile, + assets) + + /** Returns an application bundle from a set of cached files */ def asApplicationBundleFromCached(cached: Set[File]): ApplicationBundle = { assert(cached.size >= 1) val assets = if (cached.size == 1) Nil else cached.tail.toList @@ -161,11 +162,13 @@ object BundlerFile { } } - /** - * A webpack library bundle, containing only libraries - * @param project The project the library bundle was generated for - * @param file The file containing the application javascript - * @param assets All the assets on the application + /** A webpack library bundle, containing only libraries + * @param project + * The project the library bundle was generated for + * @param file + * The file containing the application javascript + * @param assets + * All the assets on the application */ case class Library(project: String, file: File, assets: List[java.io.File]) extends Public { val `type`: BundlerFileType = BundlerFileType.Library @@ -183,14 +186,13 @@ object BundlerFile { def fileName(entry: String): String = s"$entry$suffix" } - /** - * A webpack loader file. Allows an [[Application]] to access the dependencies bundled - * into a [[Library]] - * @param application Application to be loaded - * @param file Loader file + /** A webpack loader file. Allows an [[Application]] to access the dependencies bundled into a [[Library]] + * @param application + * Application to be loaded + * @param file + * Loader file */ - case class Loader(application: Application, file: java.io.File) - extends Public { + case class Loader(application: Application, file: java.io.File) extends Public { val `type`: BundlerFileType = BundlerFileType.Loader def project: String = application.project } @@ -204,15 +206,16 @@ object BundlerFile { def fileName(entry: String): String = s"$entry$suffix" } - /** - * A fully self-contained application bundle, including all dependencies. + /** A fully self-contained application bundle, including all dependencies. * - * @param project The project name - * @param file The file containing the application javascript - * @param assets All the assets on the application + * @param project + * The project name + * @param file + * The file containing the application javascript + * @param assets + * All the assets on the application */ - case class ApplicationBundle(project: String, file: File, assets: List[java.io.File]) - extends Public { + case class ApplicationBundle(project: String, file: File, assets: List[java.io.File]) extends Public { val `type`: BundlerFileType = BundlerFileType.ApplicationBundle val cached: ListSet[File] = CachedBundleFiles.cached(file, assets) diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/ExternalCommand.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/ExternalCommand.scala index 885483c6..0a31c602 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/ExternalCommand.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/ExternalCommand.scala @@ -5,18 +5,20 @@ import java.io.File import sbt._ import scalajsbundler.util.Commands -/** - * Attempts to smoothen platform-specific differences when invoking commands. +/** Attempts to smoothen platform-specific differences when invoking commands. * - * @param name Name of the command to run + * @param name + * Name of the command to run */ class ExternalCommand(name: String) { - /** - * Runs the command `cmd` - * @param args Command arguments - * @param workingDir Working directory of the process - * @param logger Logger + /** Runs the command `cmd` + * @param args + * Command arguments + * @param workingDir + * Working directory of the process + * @param logger + * Logger */ def run(args: String*)(workingDir: File, logger: Logger): Unit = Commands.run(cmd ++: args, workingDir, logger) @@ -36,10 +38,10 @@ object ExternalCommand { private val yarnOptions = List("--non-interactive", "--mutex", "network") private def syncLockfile( - lockFileName: String, - baseDir: File, - installDir: File, - logger: Logger + lockFileName: String, + baseDir: File, + installDir: File, + logger: Logger )( command: => Unit ): Unit = { @@ -60,46 +62,50 @@ object ExternalCommand { } private def syncYarnLockfile( - baseDir: File, - installDir: File, - logger: Logger + baseDir: File, + installDir: File, + logger: Logger )( - command: => Unit + command: => Unit ): Unit = { syncLockfile("yarn.lock", baseDir, installDir, logger)(command) } private def syncNpmLockfile( - baseDir: File, - installDir: File, - logger: Logger + baseDir: File, + installDir: File, + logger: Logger )( - command: => Unit + command: => Unit ): Unit = { syncLockfile("package-lock.json", baseDir, installDir, logger)(command) } - /** - * Locally install NPM packages + /** Locally install NPM packages * - * @param baseDir The (sub-)project directory which contains yarn.lock - * @param installDir The directory in which to install the packages - * @param useYarn Whether to use yarn or npm - * @param logger sbt logger - * @param npmExtraArgs Additional arguments to pass to npm - * @param npmPackages Packages to install (e.g. "webpack", "webpack@2.2.1") + * @param baseDir + * The (sub-)project directory which contains yarn.lock + * @param installDir + * The directory in which to install the packages + * @param useYarn + * Whether to use yarn or npm + * @param logger + * sbt logger + * @param npmExtraArgs + * Additional arguments to pass to npm + * @param npmPackages + * Packages to install (e.g. "webpack", "webpack@2.2.1") */ - def addPackages(baseDir: File, - installDir: File, - useYarn: Boolean, - logger: Logger, - npmExtraArgs: Seq[String], - yarnExtraArgs: Seq[String])(npmPackages: String*): Unit = + def addPackages( + baseDir: File, + installDir: File, + useYarn: Boolean, + logger: Logger, + npmExtraArgs: Seq[String], + yarnExtraArgs: Seq[String])(npmPackages: String*): Unit = if (useYarn) { syncYarnLockfile(baseDir, installDir, logger) { - Yarn.run("add" +: (yarnOptions ++ yarnExtraArgs ++ npmPackages): _*)( - installDir, - logger) + Yarn.run("add" +: (yarnOptions ++ yarnExtraArgs ++ npmPackages): _*)(installDir, logger) } } else { syncNpmLockfile(baseDir, installDir, logger) { @@ -107,16 +113,16 @@ object ExternalCommand { } } - def install(baseDir: File, - installDir: File, - useYarn: Boolean, - logger: Logger, - npmExtraArgs: Seq[String], - yarnExtraArgs: Seq[String]): Unit = + def install( + baseDir: File, + installDir: File, + useYarn: Boolean, + logger: Logger, + npmExtraArgs: Seq[String], + yarnExtraArgs: Seq[String]): Unit = if (useYarn) { syncYarnLockfile(baseDir, installDir, logger) { - Yarn.run("install" +: (yarnOptions ++ yarnExtraArgs): _*)(installDir, - logger) + Yarn.run("install" +: (yarnOptions ++ yarnExtraArgs): _*)(installDir, logger) } } else { syncNpmLockfile(baseDir, installDir, logger) { diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/NpmPackage.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/NpmPackage.scala index fb86b5f2..ef0601ff 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/NpmPackage.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/NpmPackage.scala @@ -6,16 +6,18 @@ import sbt._ import scala.util.Try case class NpmPackage(version: String) { + def major: Option[Int] = { val r = """^(\d+)(\..*|)$""".r version match { case r(v, _) => Try(v.toInt).toOption - case _ => None + case _ => None } } } object NpmPackage { + implicit val npmPackageDeserializer: Reads[NpmPackage] = (JsPath \ "version").read[String].map(NpmPackage.apply) @@ -24,4 +26,4 @@ object NpmPackage { Try(Json.parse(IO.read(webpackPackageJsonFilePath)).as[NpmPackage]).toOption } -} \ No newline at end of file +} diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/PackageJson.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/PackageJson.scala index d8b34ab9..b59a8276 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/PackageJson.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/PackageJson.scala @@ -6,32 +6,40 @@ import scalajsbundler.util.JSON object PackageJson { - /** - * Write a package.json file defining the NPM dependencies of the application, plus the ones - * required to do the bundling. + /** Write a package.json file defining the NPM dependencies of the application, plus the ones required to do the + * bundling. * - * @param log Logger - * @param targetFile File to write into - * @param npmDependencies NPM dependencies - * @param npmDevDependencies NPM devDependencies - * @param npmResolutions Resolutions to use in case of conflicting dependencies - * @param additionalNpmConfig Additional options to include in 'package.json' - * @param fullClasspath Classpath (used to look for dependencies of Scala.js libraries this project depends on) - * @param currentConfiguration Current configuration - * @return The created package.json file + * @param log + * Logger + * @param targetFile + * File to write into + * @param npmDependencies + * NPM dependencies + * @param npmDevDependencies + * NPM devDependencies + * @param npmResolutions + * Resolutions to use in case of conflicting dependencies + * @param additionalNpmConfig + * Additional options to include in 'package.json' + * @param fullClasspath + * Classpath (used to look for dependencies of Scala.js libraries this project depends on) + * @param currentConfiguration + * Current configuration + * @return + * The created package.json file */ def write( - log: Logger, - targetFile: File, - npmDependencies: Seq[(String, String)], - npmDevDependencies: Seq[(String, String)], - npmResolutions: Map[String, String], - additionalNpmConfig: Map[String, JSON], - fullClasspath: Seq[Attributed[File]], - currentConfiguration: Configuration, - webpackVersion: String, - webpackDevServerVersion: String, - webpackCliVersion: String + log: Logger, + targetFile: File, + npmDependencies: Seq[(String, String)], + npmDevDependencies: Seq[(String, String)], + npmResolutions: Map[String, String], + additionalNpmConfig: Map[String, JSON], + fullClasspath: Seq[Attributed[File]], + currentConfiguration: Configuration, + webpackVersion: String, + webpackDevServerVersion: String, + webpackCliVersion: String ): Unit = { val npmManifestDependencies = NpmDependencies.collectFromClasspath(fullClasspath) val dependencies = @@ -63,8 +71,8 @@ object PackageJson { JSON.obj( ( additionalNpmConfig.toSeq :+ - "dependencies" -> JSON.objStr(resolveDependencies(dependencies, npmResolutions, log)) :+ - "devDependencies" -> JSON.objStr(resolveDependencies(devDependencies, npmResolutions, log)) + "dependencies" -> JSON.objStr(resolveDependencies(dependencies, npmResolutions, log)) :+ + "devDependencies" -> JSON.objStr(resolveDependencies(devDependencies, npmResolutions, log)) ): _* ) @@ -72,23 +80,26 @@ object PackageJson { IO.write(targetFile, packageJson.toJson) } - /** - * Resolves multiple occurrences of a dependency to a same package. + /** Resolves multiple occurrences of a dependency to a same package. * - * - If all the occurrences refer to the same version, pick this one ; - * - If they refer to different versions, pick the one defined in `resolutions` (or fail - * if there is no such resolution). + * - If all the occurrences refer to the same version, pick this one ; + * - If they refer to different versions, pick the one defined in `resolutions` (or fail if there is no such + * resolution). * - * @return The resolved dependencies - * @param dependencies The dependencies to resolve - * @param resolutions The resolutions to use in case of conflict (they will be ignored if there are no conflicts) - * @param log Logger + * @return + * The resolved dependencies + * @param dependencies + * The dependencies to resolve + * @param resolutions + * The resolutions to use in case of conflict (they will be ignored if there are no conflicts) + * @param log + * Logger */ def resolveDependencies( - dependencies: Seq[(String, String)], - resolutions: Map[String, String], - log: Logger - ): List[(String, String)] ={ + dependencies: Seq[(String, String)], + resolutions: Map[String, String], + log: Logger + ): List[(String, String)] = { val resolvedDependencies = dependencies .groupBy { case (name, version) => name } @@ -101,7 +112,7 @@ object PackageJson { case _ => val resolution = resolutions.get(name) match { case Some(v) => v - case None => versions.mkString(" ") + case None => versions.mkString(" ") } name -> resolution } diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/Stats.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/Stats.scala index 9f0ab4e1..a80eaddd 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/Stats.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/Stats.scala @@ -1,4 +1,3 @@ - package scalajsbundler import play.api.libs.json._ @@ -8,12 +7,11 @@ import scala.math.max import java.io.File import java.nio.file.Path -/** - * Webpack stats model and json parsers - */ +/** Webpack stats model and json parsers */ object Stats { final case class Asset(name: String, size: Long, emitted: Boolean, chunkNames: List[String]) { + def formattedSize: String = { val oneKiB = 1024L val oneMiB = oneKiB * oneKiB @@ -27,6 +25,7 @@ object Stats { object formatting { final case class Part(t: String, l: Int) { + def maxL(p: Part): Part = copy(l = max(l, p.l)) @@ -40,7 +39,9 @@ object Stats { } final case class AssetLine(asset: Part, size: Part, emitted: Part, chunks: Part) { - def adjustPadding(p: AssetLine): AssetLine = copy(asset.maxL(p.asset), size.maxL(p.size), emitted.maxL(p.emitted), chunks.maxL(p.chunks)) + + def adjustPadding(p: AssetLine): AssetLine = + copy(asset.maxL(p.asset), size.maxL(p.size), emitted.maxL(p.emitted), chunks.maxL(p.chunks)) def show: String = List(asset, size, emitted, chunks).map(_.leftPad).mkString(" ") } @@ -55,84 +56,82 @@ object Stats { final case class WebpackWarning(moduleName: String, message: String) final case class WebpackStats( - version: String, - hash: String, - time: Long, - outputPath: Option[Path], - errors: List[WebpackError], - warnings: List[WebpackWarning], - assets: List[Asset] + version: String, + hash: String, + time: Long, + outputPath: Option[Path], + errors: List[WebpackError], + warnings: List[WebpackWarning], + assets: List[Asset] ) { - /** - * Prints to the log an output similar to what webpack pushes to stdout - */ + /** Prints to the log an output similar to what webpack pushes to stdout */ def print(log: Logger): Unit = { import formatting._ // Print base info - List(s"Version: $version", s"Hash: $hash", s"Time: ${time}ms", s"Path: ${outputPath.getOrElse("")}").foreach(x => log.info(x)) + List(s"Version: $version", s"Hash: $hash", s"Time: ${time}ms", s"Path: ${outputPath.getOrElse("")}") + .foreach(x => log.info(x)) log.info("") // Print the assets - assets.map { a => - val emitted = if (a.emitted) "[emitted]" else "" - AssetLine(Part(a.name), Part(a.formattedSize), Part(emitted), Part(a.chunkNames.mkString("[", ",", "]"))) - }.foldLeft(List(AssetLine.Zero)) { - case (lines, curr) => + assets + .map { a => + val emitted = if (a.emitted) "[emitted]" else "" + AssetLine(Part(a.name), Part(a.formattedSize), Part(emitted), Part(a.chunkNames.mkString("[", ",", "]"))) + } + .foldLeft(List(AssetLine.Zero)) { case (lines, curr) => val adj = lines.map(_.adjustPadding(curr)) val adjNew = adj.headOption.fold(curr)(curr.adjustPadding) (adjNew :: adj.reverse).reverse - }.foreach { l => - log.info(l.show) - } + } + .foreach { l => + log.info(l.show) + } log.info("") } - /** - * Attempts to find the name of the asset for the project name - * Note that we only search on files ending on .js skipping e.g. map files + /** Attempts to find the name of the asset for the project name Note that we only search on files ending on .js + * skipping e.g. map files */ def assetName(project: String): Option[String] = assets.find(a => a.chunkNames.contains(project) && a.name.endsWith(".js")).map(_.name) - /** - * Resolve the asset on the output path or the target dir if unavailable - */ + /** Resolve the asset on the output path or the target dir if unavailable */ def resolveAsset(altDir: Path, asset: String): Option[File] = assetName(asset).map(a => outputPath.getOrElse(altDir).resolve(a).toFile) - /** - * Resolve alles asset on the output path or the target dir if unavailable - */ + /** Resolve alles asset on the output path or the target dir if unavailable */ def resolveAllAssets(altDir: Path): List[File] = assets.map(a => outputPath.getOrElse(altDir).resolve(a.name).toFile) } implicit val assetsReads: Reads[Asset] = ( (JsPath \ "name").read[String] and - (JsPath \ "size").read[Long] and - (JsPath \ "emitted").read[Boolean] and - (JsPath \ "chunkNames").read[List[String]] + (JsPath \ "size").read[Long] and + (JsPath \ "emitted").read[Boolean] and + (JsPath \ "chunkNames").read[List[String]] )(Asset.apply _) implicit val errorReads: Reads[WebpackError] = ( (JsPath \ "moduleName").read[String] and (JsPath \ "message").read[String] and (JsPath \ "loc").read[String] - )(WebpackError.apply _) + )(WebpackError.apply _) implicit val warningReads: Reads[WebpackWarning] = ( (JsPath \ "moduleName").read[String] and (JsPath \ "message").read[String] - )(WebpackWarning.apply _) + )(WebpackWarning.apply _) implicit val statsReads: Reads[WebpackStats] = ( (JsPath \ "version").read[String] and - (JsPath \ "hash").read[String] and - (JsPath \ "time").read[Long] and - (JsPath \ "outputPath").readNullable[String].map(x => x.map(new File(_).toPath)) and // It seems webpack 2 doesn't produce outputPath - (JsPath \ "errors").read[List[WebpackError]] and - (JsPath \ "warnings").read[List[WebpackWarning]] and - (JsPath \ "assets").read[List[Asset]] + (JsPath \ "hash").read[String] and + (JsPath \ "time").read[Long] and + (JsPath \ "outputPath") + .readNullable[String] + .map(x => x.map(new File(_).toPath)) and // It seems webpack 2 doesn't produce outputPath + (JsPath \ "errors").read[List[WebpackError]] and + (JsPath \ "warnings").read[List[WebpackWarning]] and + (JsPath \ "assets").read[List[Asset]] )(WebpackStats.apply _) } diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/Webpack.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/Webpack.scala index da50694f..7cb65899 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/Webpack.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/Webpack.scala @@ -10,29 +10,37 @@ import Stats._ import scala.util.{Failure, Success, Try} object Webpack { + // Represents webpack 5 modes sealed trait WebpackMode { def mode: String } + case object DevelopmentMode extends WebpackMode { val mode = "development" } + case object ProductionMode extends WebpackMode { val mode = "production" } + object WebpackMode { + def fromBooleanProductionMode(productionMode: Boolean): WebpackMode = if (productionMode) ProductionMode else DevelopmentMode } - /** - * Copies the custom webpack configuration file and the webpackResources to the target dir + /** Copies the custom webpack configuration file and the webpackResources to the target dir * - * @param targetDir target directory - * @param webpackResources Resources to copy - * @param customConfigFile User supplied config file - * @return The copied config file. + * @param targetDir + * target directory + * @param webpackResources + * Resources to copy + * @param customConfigFile + * User supplied config file + * @return + * The copied config file. */ def copyCustomWebpackConfigFiles(targetDir: File, webpackResources: Seq[File])(customConfigFile: File): File = { def copyToWorkingDir(targetDir: File)(file: File): File = { @@ -45,39 +53,43 @@ object Webpack { copyToWorkingDir(targetDir)(customConfigFile) } - /** - * Writes the webpack configuration file. The output file is designed to be minimal, and to be extended, - * however, the `entry` and `output` keys must be preserved in order for the bundler to work as expected. + /** Writes the webpack configuration file. The output file is designed to be minimal, and to be extended, however, the + * `entry` and `output` keys must be preserved in order for the bundler to work as expected. * - * @param emitSourceMaps Whether source maps is enabled at all - * @param entry The input entrypoint file to process via webpack - * @param webpackConfigFile webpack configuration file to write to - * @param libraryBundleName If defined, generate a library bundle named `libraryBundleName` - * @param log Logger + * @param emitSourceMaps + * Whether source maps is enabled at all + * @param entry + * The input entrypoint file to process via webpack + * @param webpackConfigFile + * webpack configuration file to write to + * @param libraryBundleName + * If defined, generate a library bundle named `libraryBundleName` + * @param log + * Logger */ def writeConfigFile( - emitSourceMaps: Boolean, - entry: BundlerFile.WebpackInput, - webpackConfigFile: BundlerFile.WebpackConfig, - libraryBundleName: Option[String], - mode: WebpackMode, - devServerPort: Int, - log: Logger + emitSourceMaps: Boolean, + entry: BundlerFile.WebpackInput, + webpackConfigFile: BundlerFile.WebpackConfig, + libraryBundleName: Option[String], + mode: WebpackMode, + devServerPort: Int, + log: Logger ): Unit = { - val webpackConfigContent = generateConfigFile(emitSourceMaps, entry, webpackConfigFile, libraryBundleName, mode, - devServerPort) + val webpackConfigContent = + generateConfigFile(emitSourceMaps, entry, webpackConfigFile, libraryBundleName, mode, devServerPort) log.info("Writing scalajs.webpack.config.js") IO.write(webpackConfigFile.file, webpackConfigContent.show) } private def generateConfigFile( - emitSourceMaps: Boolean, - entry: BundlerFile.WebpackInput, - webpackConfigFile: BundlerFile.WebpackConfig, - libraryBundleName: Option[String], - mode: WebpackMode, - devServerPort: Int + emitSourceMaps: Boolean, + entry: BundlerFile.WebpackInput, + webpackConfigFile: BundlerFile.WebpackConfig, + libraryBundleName: Option[String], + mode: WebpackMode, + devServerPort: Int ): JS = { val webpackNpmPackage = NpmPackage.getForModule(webpackConfigFile.targetDir.toFile, "webpack") webpackNpmPackage.flatMap(_.major) match { @@ -99,28 +111,30 @@ object Webpack { ) } - JS.ref("module").dot("exports").assign(JS.obj(Seq( - "entry" -> JS.obj( - entry.project -> JS.arr(JS.str(entry.file.absolutePath)) - ), - "output" -> output, - "mode" -> JS.str(mode.mode), - "devServer" -> JS.obj("port" -> JS.int(devServerPort)), - ) ++ ( - if (emitSourceMaps) { - Seq( - "devtool" -> JS.str("source-map"), - "module" -> JS.obj( - "rules" -> JS.arr( - JS.obj( - "test" -> JS.regex("\\.js$"), - "enforce" -> JS.str("pre"), - "use" -> JS.arr(JS.str("source-map-loader")) + JS.ref("module") + .dot("exports") + .assign(JS.obj(Seq( + "entry" -> JS.obj( + entry.project -> JS.arr(JS.str(entry.file.absolutePath)) + ), + "output" -> output, + "mode" -> JS.str(mode.mode), + "devServer" -> JS.obj("port" -> JS.int(devServerPort)) + ) ++ ( + if (emitSourceMaps) { + Seq( + "devtool" -> JS.str("source-map"), + "module" -> JS.obj( + "rules" -> JS.arr( + JS.obj( + "test" -> JS.regex("\\.js$"), + "enforce" -> JS.str("pre"), + "use" -> JS.arr(JS.str("source-map-loader")) + ) ) ) ) - ) - } else Nil + } else Nil ): _*)) case Some(x) => @@ -131,33 +145,43 @@ object Webpack { } } - /** - * Run webpack to bundle the application. + /** Run webpack to bundle the application. * - * @param emitSourceMaps Whether or not source maps are enabled - * @param generatedWebpackConfigFile Webpack config file generated by scalajs-bundler - * @param customWebpackConfigFile User supplied config file - * @param webpackResources Additional resources to be copied to the working folder - * @param entry Scala.js application to bundle - * @param targetDir Target directory (and working directory for Nodejs) - * @param extraArgs Extra arguments passed to webpack - * @param mode Mode for webpack 5 - * @param devServerPort Port used by webpack-dev-server - * @param log Logger - * @return The generated bundles + * @param emitSourceMaps + * Whether or not source maps are enabled + * @param generatedWebpackConfigFile + * Webpack config file generated by scalajs-bundler + * @param customWebpackConfigFile + * User supplied config file + * @param webpackResources + * Additional resources to be copied to the working folder + * @param entry + * Scala.js application to bundle + * @param targetDir + * Target directory (and working directory for Nodejs) + * @param extraArgs + * Extra arguments passed to webpack + * @param mode + * Mode for webpack 5 + * @param devServerPort + * Port used by webpack-dev-server + * @param log + * Logger + * @return + * The generated bundles */ def bundle( - emitSourceMaps: Boolean, - generatedWebpackConfigFile: BundlerFile.WebpackConfig, - customWebpackConfigFile: Option[File], - webpackResources: Seq[File], - entry: BundlerFile.Application, - targetDir: File, - extraArgs: Seq[String], - nodeArgs: Seq[String], - mode: WebpackMode, - devServerPort: Int, - log: Logger + emitSourceMaps: Boolean, + generatedWebpackConfigFile: BundlerFile.WebpackConfig, + customWebpackConfigFile: Option[File], + webpackResources: Seq[File], + entry: BundlerFile.Application, + targetDir: File, + extraArgs: Seq[String], + nodeArgs: Seq[String], + mode: WebpackMode, + devServerPort: Int, + log: Logger ): BundlerFile.ApplicationBundle = { writeConfigFile(emitSourceMaps, entry, generatedWebpackConfigFile, None, mode, devServerPort, log) @@ -177,32 +201,41 @@ object Webpack { bundle } - /** - * Run webpack to bundle the application. + /** Run webpack to bundle the application. * - * @param emitSourceMaps Are source maps enabled? - * @param generatedWebpackConfigFile Webpack config file generated by scalajs-bundler - * @param customWebpackConfigFile User supplied config file - * @param webpackResources Additional webpack resources to include in the working directory - * @param entryPointFile The entrypoint file to bundle dependencies for - * @param libraryModuleName The library module name to assign the webpack bundle to - * @param extraArgs Extra arguments passed to webpack - * @param mode Mode for webpack 5 - * @param log Logger - * @return The generated bundle + * @param emitSourceMaps + * Are source maps enabled? + * @param generatedWebpackConfigFile + * Webpack config file generated by scalajs-bundler + * @param customWebpackConfigFile + * User supplied config file + * @param webpackResources + * Additional webpack resources to include in the working directory + * @param entryPointFile + * The entrypoint file to bundle dependencies for + * @param libraryModuleName + * The library module name to assign the webpack bundle to + * @param extraArgs + * Extra arguments passed to webpack + * @param mode + * Mode for webpack 5 + * @param log + * Logger + * @return + * The generated bundle */ def bundleLibraries( - emitSourceMaps: Boolean, - generatedWebpackConfigFile: BundlerFile.WebpackConfig, - customWebpackConfigFile: Option[File], - webpackResources: Seq[File], - entryPointFile: BundlerFile.EntryPoint, - libraryModuleName: String, - extraArgs: Seq[String], - nodeArgs: Seq[String], - mode: WebpackMode, - devServerPort: Int, - log: Logger + emitSourceMaps: Boolean, + generatedWebpackConfigFile: BundlerFile.WebpackConfig, + customWebpackConfigFile: Option[File], + webpackResources: Seq[File], + entryPointFile: BundlerFile.EntryPoint, + libraryModuleName: String, + extraArgs: Seq[String], + nodeArgs: Seq[String], + mode: WebpackMode, + devServerPort: Int, + log: Logger ): BundlerFile.Library = { writeConfigFile( emitSourceMaps, @@ -235,8 +268,8 @@ object Webpack { case JsError(e) => logger.error("Error parsing webpack stats output") // In case of error print the result and return None. it will be ignored upstream - e.foreach { - case (p, v) => logger.error(s"$p: ${v.mkString(",")}") + e.foreach { case (p, v) => + logger.error(s"$p: ${v.mkString(",")}") } None case JsSuccess(p, _) => @@ -271,13 +304,16 @@ object Webpack { } } - /** - * Runs the webpack command. + /** Runs the webpack command. * - * @param nodeArgs node.js cli flags - * @param args Arguments to pass to the webpack command - * @param workingDir Working directory in which the Nodejs will be run (where there is the `node_modules` subdirectory) - * @param log Logger + * @param nodeArgs + * node.js cli flags + * @param args + * Arguments to pass to the webpack command + * @param workingDir + * Working directory in which the Nodejs will be run (where there is the `node_modules` subdirectory) + * @param log + * Logger */ def run(nodeArgs: String*)(args: String*)(workingDir: File, log: Logger): Option[WebpackStats] = { val webpackBin = workingDir / "node_modules" / "webpack" / "bin" / "webpack" diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/WebpackDevServer.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/WebpackDevServer.scala index a6a8cb56..92e69dd8 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/WebpackDevServer.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/WebpackDevServer.scala @@ -3,50 +3,55 @@ package scalajsbundler import sbt._ import java.io.File -/** - * Simple wrapper over webpack-dev-server - */ -private [scalajsbundler] class WebpackDevServer { +/** Simple wrapper over webpack-dev-server */ +private[scalajsbundler] class WebpackDevServer { private var worker: Option[Worker] = None - /** - * @param workDir - path to working directory for webpack-dev-server - * @param configPath - path to webpack config. - * @param extraArgs - additional arguments for webpack-dev-server. - * @param logger - a logger to use for output - * @param globalLogger - a global logger to use for output even when the task is terminated + /** @param workDir + * path to working directory for webpack-dev-server + * @param configPath + * path to webpack config. + * @param extraArgs + * additional arguments for webpack-dev-server. + * @param logger + * a logger to use for output + * @param globalLogger + * a global logger to use for output even when the task is terminated */ def start( - workDir: File, - configPath: File, - extraArgs: Seq[String], - logger: Logger, - globalLogger: Logger, + workDir: File, + configPath: File, + extraArgs: Seq[String], + logger: Logger, + globalLogger: Logger ) = this.synchronized { stop() - worker = Some(new Worker( - workDir, - configPath, - extraArgs, - logger, - globalLogger - )) + worker = Some( + new Worker( + workDir, + configPath, + extraArgs, + logger, + globalLogger + )) } def stop() = this.synchronized { - worker.foreach { w => { - w.stop() - worker = None - }} + worker.foreach { w => + { + w.stop() + worker = None + } + } } private class Worker( - workDir: File, - configPath: File, - extraArgs: Seq[String], - logger: Logger, - globalLogger: Logger, + workDir: File, + configPath: File, + extraArgs: Seq[String], + logger: Logger, + globalLogger: Logger ) { logger.info("Starting webpack-dev-server"); diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/WebpackEntryPoint.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/WebpackEntryPoint.scala index c5cae695..31b6fc54 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/WebpackEntryPoint.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/WebpackEntryPoint.scala @@ -6,10 +6,12 @@ import scalajsbundler.util.JS object WebpackEntryPoint { - /** - * @return The written loader file (faking a `require` implementation) - * @param entryPoint File to write the loader to - * @param logger Logger + /** @return + * The written loader file (faking a `require` implementation) + * @param entryPoint + * File to write the loader to + * @param logger + * Logger */ def writeEntryPoint( imports: Seq[String], @@ -25,8 +27,8 @@ object WebpackEntryPoint { Seq( "require" -> JS.fun(name => JS.obj(imports.map { moduleName => - moduleName -> JS.ref("require").apply(JS.str(moduleName)) - }: _*) + moduleName -> JS.ref("require").apply(JS.str(moduleName)) + }: _*) .bracket(name))): _*) ) IO.write(entryPoint.file, depsFileContent.show) diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/LibraryTasks.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/LibraryTasks.scala index 307eb199..1c766ae5 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/LibraryTasks.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/LibraryTasks.scala @@ -11,26 +11,26 @@ import scalajsbundler.util.{Caching, JSBundler} object LibraryTasks { - private[sbtplugin] def entryPoint(stage: TaskKey[Attributed[File]]) - : Def.Initialize[Task[BundlerFile.EntryPoint]] = - Def.task { - val s = streams.value - val importedModules = (scalaJSBundlerImportedModules in stage).value - val entry = WebpackTasks.entry(stage).value - val cacheLocation = streams.value.cacheDirectory / s"${stage.key.label}-webpack-entrypoint" - val entryPointFile = entry.asEntryPoint + private[sbtplugin] def entryPoint(stage: TaskKey[Attributed[File]]): Def.Initialize[Task[BundlerFile.EntryPoint]] = + Def + .task { + val s = streams.value + val importedModules = (scalaJSBundlerImportedModules in stage).value + val entry = WebpackTasks.entry(stage).value + val cacheLocation = streams.value.cacheDirectory / s"${stage.key.label}-webpack-entrypoint" + val entryPointFile = entry.asEntryPoint - // Avoid re-writing the entrypoint file if the list of modules hasn't changed - // allowing downstream caching to detect change reliably - Caching.cached(entryPointFile.file, importedModules.mkString(","), cacheLocation)( - () => + // Avoid re-writing the entrypoint file if the list of modules hasn't changed + // allowing downstream caching to detect change reliably + Caching.cached(entryPointFile.file, importedModules.mkString(","), cacheLocation)(() => WebpackEntryPoint.writeEntryPoint( importedModules, entryPointFile, s.log - )) - entryPointFile - }.dependsOn(npmUpdate in stage) + )) + entryPointFile + } + .dependsOn(npmUpdate in stage) private[sbtplugin] def bundle( stage: TaskKey[Attributed[File]], @@ -62,19 +62,21 @@ object LibraryTasks { ) { _ => log.info(s"Building webpack library bundles for ${entryPointFile.project} in $cacheLocation") - Webpack.bundleLibraries( - emitSourceMaps, - generatedWebpackConfigFile, - customWebpackConfigFile, - webpackResourceFiles, - entryPointFile, - mode.exportedName, - extraArgs, - nodeArgs, - webpackMode, - devServerPort, - log - ).cached + Webpack + .bundleLibraries( + emitSourceMaps, + generatedWebpackConfigFile, + customWebpackConfigFile, + webpackResourceFiles, + entryPointFile, + mode.exportedName, + extraArgs, + nodeArgs, + webpackMode, + devServerPort, + log + ) + .cached } val cached = cachedActionFunction(monitoredFiles.to[Set]) generatedWebpackConfigFile.asLibraryFromCached(cached) @@ -95,9 +97,9 @@ object LibraryTasks { loaderFile } - private[sbtplugin] def bundleAll(stage: TaskKey[Attributed[File]], - mode: BundlingMode.LibraryAndApplication) - : Def.Initialize[Task[Seq[BundlerFile.Public]]] = + private[sbtplugin] def bundleAll( + stage: TaskKey[Attributed[File]], + mode: BundlingMode.LibraryAndApplication): Def.Initialize[Task[Seq[BundlerFile.Public]]] = Def.task { assert(ensureModuleKindIsCommonJSModule.value) val cacheLocation = streams.value.cacheDirectory / s"${stage.key.label}-webpack-bundle-all" @@ -113,32 +115,32 @@ object LibraryTasks { cacheLocation, inStyle = FilesInfo.hash ) { _ => - JSBundler.bundle( - targetDir = targetDir, - entry, - library, - emitSourceMaps, - mode.exportedName, - log - ).cached + JSBundler + .bundle( + targetDir = targetDir, + entry, + library, + emitSourceMaps, + mode.exportedName, + log + ) + .cached } val cached = cachedActionFunction(filesToMonitor.toSet) Seq(entry.asApplicationBundleFromCached(cached), entry.asLoader, library, entry) } - private[sbtplugin] def librariesAndLoaders(stage: TaskKey[Attributed[File]], - mode: BundlingMode.LibraryOnly) - : Def.Initialize[Task[Seq[Attributed[File]]]] = + private[sbtplugin] def librariesAndLoaders( + stage: TaskKey[Attributed[File]], + mode: BundlingMode.LibraryOnly): Def.Initialize[Task[Seq[Attributed[File]]]] = Def.task { - Seq(WebpackTasks.entry(stage).value, - loader(stage, mode).value, - bundle(stage, mode).value).flatMap(_.asAttributedFiles) + Seq(WebpackTasks.entry(stage).value, loader(stage, mode).value, bundle(stage, mode).value) + .flatMap(_.asAttributedFiles) } private[sbtplugin] def libraryAndLoadersBundle( stage: TaskKey[Attributed[File]], - mode: BundlingMode.LibraryAndApplication) - : Def.Initialize[Task[Seq[Attributed[File]]]] = + mode: BundlingMode.LibraryAndApplication): Def.Initialize[Task[Seq[Attributed[File]]]] = Def.task { bundleAll(stage, mode).value.flatMap(_.asAttributedFiles) } diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/NpmUpdateTasks.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/NpmUpdateTasks.scala index cb4d5612..8cca16f6 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/NpmUpdateTasks.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/NpmUpdateTasks.scala @@ -6,49 +6,64 @@ import sbt._ object NpmUpdateTasks { - /** - * Runs either `npm install` or `yarn install` and installs JavaScript resources as node packages. + /** Runs either `npm install` or `yarn install` and installs JavaScript resources as node packages. * - * @param targetDir npm directory - * @param packageJsonFile Json file containing NPM dependencies - * @param useYarn Whether to use yarn or npm - * @param jsResources A sequence of JavaScript resources - * @param streams A sbt TaskStream - * @param npmExtraArgs Additional arguments to pass to npm - * @param yarnExtraArgs Additional arguments to pass to yarn - * @return The written npm directory + * @param targetDir + * npm directory + * @param packageJsonFile + * Json file containing NPM dependencies + * @param useYarn + * Whether to use yarn or npm + * @param jsResources + * A sequence of JavaScript resources + * @param streams + * A sbt TaskStream + * @param npmExtraArgs + * Additional arguments to pass to npm + * @param yarnExtraArgs + * Additional arguments to pass to yarn + * @return + * The written npm directory */ - def npmUpdate(baseDir: File, - targetDir: File, - packageJsonFile: File, - useYarn: Boolean, - jsResources: Seq[(String, Path)], - streams: Keys.TaskStreams, - npmExtraArgs: Seq[String], - yarnExtraArgs: Seq[String]): File = { + def npmUpdate( + baseDir: File, + targetDir: File, + packageJsonFile: File, + useYarn: Boolean, + jsResources: Seq[(String, Path)], + streams: Keys.TaskStreams, + npmExtraArgs: Seq[String], + yarnExtraArgs: Seq[String]): File = { val dir = npmInstallDependencies(baseDir, targetDir, packageJsonFile, useYarn, streams, npmExtraArgs, yarnExtraArgs) npmInstallJSResources(targetDir, jsResources, Seq.empty, streams) dir } - /** - * Runs either `npm install` or `yarn install`. + /** Runs either `npm install` or `yarn install`. * - * @param targetDir npm directory - * @param packageJsonFile Json file containing NPM dependencies - * @param useYarn Whether to use yarn or npm - * @param streams A sbt TaskStream - * @param npmExtraArgs Additional arguments to pass to npm - * @param yarnExtraArgs Additional arguments to pass to yarn - * @return The written npm directory + * @param targetDir + * npm directory + * @param packageJsonFile + * Json file containing NPM dependencies + * @param useYarn + * Whether to use yarn or npm + * @param streams + * A sbt TaskStream + * @param npmExtraArgs + * Additional arguments to pass to npm + * @param yarnExtraArgs + * Additional arguments to pass to yarn + * @return + * The written npm directory */ - def npmInstallDependencies(baseDir: File, - targetDir: File, - packageJsonFile: File, - useYarn: Boolean, - streams: Keys.TaskStreams, - npmExtraArgs: Seq[String], - yarnExtraArgs: Seq[String]): File = { + def npmInstallDependencies( + baseDir: File, + targetDir: File, + packageJsonFile: File, + useYarn: Boolean, + streams: Keys.TaskStreams, + npmExtraArgs: Seq[String], + yarnExtraArgs: Seq[String]): File = { val log = streams.log val cachedActionFunction = FileFunction.cached( @@ -64,6 +79,7 @@ object NpmUpdateTasks { } private object PathWithFile { + def unapply(path: Path): Option[File] = { try { Some(path.toFile()) @@ -73,20 +89,24 @@ object NpmUpdateTasks { } } - /** - * Installs JavaScript resources as node packages. + /** Installs JavaScript resources as node packages. * - * @param targetDir npm directory - * @param jsResources The JavaScript resources - * @param streams A sbt TaskStream - * @return The paths to the node packages + * @param targetDir + * npm directory + * @param jsResources + * The JavaScript resources + * @param streams + * A sbt TaskStream + * @return + * The paths to the node packages */ - def npmInstallJSResources(targetDir: File, - jsResources: Seq[(String, Path)], - jsSourceDirectories: Seq[File], - streams: Keys.TaskStreams): Seq[File] = { - val jsFileResources = jsResources.collect { - case (relativePath, PathWithFile(jsfile)) => jsfile -> relativePath + def npmInstallJSResources( + targetDir: File, + jsResources: Seq[(String, Path)], + jsSourceDirectories: Seq[File], + streams: Keys.TaskStreams): Seq[File] = { + val jsFileResources = jsResources.collect { case (relativePath, PathWithFile(jsfile)) => + jsfile -> relativePath }.toSet ++ jsSourceDirectories.flatMap { f => if (f.isDirectory) sbt.Path.allSubpaths(f).filterNot(_._1.isDirectory) @@ -94,15 +114,15 @@ object NpmUpdateTasks { }.toSet val cachedActionFunction = FileFunction.cached( - streams.cacheDirectory / "scalajsbundler-npm-install-resources", - inStyle = FilesInfo.hash - ) { _ => + streams.cacheDirectory / "scalajsbundler-npm-install-resources", + inStyle = FilesInfo.hash + ) { _ => jsFileResources.map { case (file, relativePath) => val resourcePath = targetDir / relativePath IO.copyFile(file, resourcePath) resourcePath - } } + } val files = jsFileResources.map { case (rFile, _) => rFile } cachedActionFunction(files).toSeq } diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/PackageJsonTasks.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/PackageJsonTasks.scala index e9754efd..bb1a107e 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/PackageJsonTasks.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/PackageJsonTasks.scala @@ -7,31 +7,40 @@ import scalajsbundler.util.{Caching, JSON} object PackageJsonTasks { - /** - * Writes the package.json file that describes the project dependencies - * @param targetDir Directory in which write the file - * @param npmDependencies NPM dependencies - * @param npmDevDependencies NPM devDependencies - * @param npmResolutions Resolutions to use in case of conflicts - * @param additionalNpmConfig Additional options to include in 'package.json' - * @param fullClasspath Classpath - * @param configuration Current configuration (Compile or Test) - * @param webpackVersion Webpack version - * @param webpackDevServerVersion Webpack development server version - * @return The written package.json file + /** Writes the package.json file that describes the project dependencies + * @param targetDir + * Directory in which write the file + * @param npmDependencies + * NPM dependencies + * @param npmDevDependencies + * NPM devDependencies + * @param npmResolutions + * Resolutions to use in case of conflicts + * @param additionalNpmConfig + * Additional options to include in 'package.json' + * @param fullClasspath + * Classpath + * @param configuration + * Current configuration (Compile or Test) + * @param webpackVersion + * Webpack version + * @param webpackDevServerVersion + * Webpack development server version + * @return + * The written package.json file */ def writePackageJson( - targetDir: File, - npmDependencies: Seq[(String, String)], - npmDevDependencies: Seq[(String, String)], - npmResolutions: Map[String, String], - additionalNpmConfig: Map[String, JSON], - fullClasspath: Seq[Attributed[File]], - configuration: Configuration, - webpackVersion: String, - webpackDevServerVersion: String, - webpackCliVersion: String, - streams: Keys.TaskStreams + targetDir: File, + npmDependencies: Seq[(String, String)], + npmDevDependencies: Seq[(String, String)], + npmResolutions: Map[String, String], + additionalNpmConfig: Map[String, JSON], + fullClasspath: Seq[Attributed[File]], + configuration: Configuration, + webpackVersion: String, + webpackDevServerVersion: String, + webpackCliVersion: String, + streams: Keys.TaskStreams ): BundlerFile.PackageJson = { val hash = Seq( diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/SBTBundlerFile.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/SBTBundlerFile.scala index 35a0bfaf..f6c6a2c5 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/SBTBundlerFile.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/SBTBundlerFile.scala @@ -4,40 +4,35 @@ import sbt.{AttributeKey, Attributed} import scalajsbundler.{BundlerFile, BundlerFileType} -/** - * SBT Specific Extensions to [[scalajsbundler.BundlerFile]] +/** SBT Specific Extensions to [[scalajsbundler.BundlerFile]] * - * @param f The BundlerFile to enrich + * @param f + * The BundlerFile to enrich */ class SBTBundlerFile(f: BundlerFile.Public) { - import SBTBundlerFile._ + import SBTBundlerFile._ - def asAttributedFiles: Seq[sbt.Attributed[sbt.File]] = { - val main = Attributed - .blank(f.attributedFiles._1) - .put(ProjectNameAttr, f.project) - .put(BundlerFileTypeAttr, f.`type`) - val assets = Attributed - .blankSeq(f.attributedFiles._2).map { - _ - .put(ProjectNameAttr, f.project) + def asAttributedFiles: Seq[sbt.Attributed[sbt.File]] = { + val main = Attributed + .blank(f.attributedFiles._1) + .put(ProjectNameAttr, f.project) + .put(BundlerFileTypeAttr, f.`type`) + val assets = Attributed + .blankSeq(f.attributedFiles._2) + .map { + _.put(ProjectNameAttr, f.project) .put(BundlerFileTypeAttr, BundlerFileType.Asset) - } - main +: assets - } + } + main +: assets + } } object SBTBundlerFile { - /** - * A string attribute describing the project and output stage (fastopt vs. opt) - */ + /** A string attribute describing the project and output stage (fastopt vs. opt) */ val ProjectNameAttr: AttributeKey[String] = AttributeKey("project-name") - /** - * A [[scalajsbundler.BundlerFileType]], allowing unambigiuous selection of output files - * downstream from the plugin. + /** A [[scalajsbundler.BundlerFileType]], allowing unambigiuous selection of output files downstream from the plugin. */ - val BundlerFileTypeAttr: AttributeKey[BundlerFileType] = AttributeKey( - "bundler-file-type") + val BundlerFileTypeAttr: AttributeKey[BundlerFileType] = AttributeKey("bundler-file-type") } diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/ScalaJSBundlerPlugin.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/ScalaJSBundlerPlugin.scala index fdda11e5..d735a2b1 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/ScalaJSBundlerPlugin.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/ScalaJSBundlerPlugin.scala @@ -9,18 +9,16 @@ import scalajsbundler.{BundlerFile, NpmDependencies, Webpack, WebpackDevServer} import scalajsbundler.ExternalCommand.addPackages import scalajsbundler.util.{JSON, ScalaJSNativeLibraries} - -/** - * This plugin enables `ScalaJSPlugin` and sets the `scalaJSModuleKind` to `CommonJSModule`. It also makes it - * possible to define dependencies to NPM packages and provides tasks to fetch them or to bundle the application - * with its dependencies. +/** This plugin enables `ScalaJSPlugin` and sets the `scalaJSModuleKind` to `CommonJSModule`. It also makes it possible + * to define dependencies to NPM packages and provides tasks to fetch them or to bundle the application with its + * dependencies. * - * = Tasks and Settings = + * =Tasks and Settings= * - * The [[ScalaJSBundlerPlugin.autoImport autoImport]] member documents the keys provided by this plugin. Besides these keys, the - * following existing keys also control the plugin: + * The [[ScalaJSBundlerPlugin.autoImport autoImport]] member documents the keys provided by this plugin. Besides these + * keys, the following existing keys also control the plugin: * - * == `version in webpack` == + * ==`version in webpack`== * * Version of webpack to use. Example: * @@ -28,11 +26,11 @@ import scalajsbundler.util.{JSON, ScalaJSNativeLibraries} * version in webpack := "3.5.5" * }}} * - * == `version in installJsdom` == + * ==`version in installJsdom`== * * Version of jsdom to use. * - * == `version in startWebpackDevServer` == + * ==`version in startWebpackDevServer`== * * Version of webpack-dev-server to use. * @@ -40,22 +38,20 @@ import scalajsbundler.util.{JSON, ScalaJSNativeLibraries} * version in startWebpackDevServer := "2.11.1" * }}} * - * == `crossTarget in npmUpdate` == + * ==`crossTarget in npmUpdate`== * - * The directory in which NPM dependencies will be fetched, and where all the .js files - * will be generated. The directory is different according to the current `Configuration` - * (either `Compile` or `Test`). + * The directory in which NPM dependencies will be fetched, and where all the .js files will be generated. The + * directory is different according to the current `Configuration` (either `Compile` or `Test`). * - * Defaults to `crossTarget.value / "scalajs-bundler" / "main"` for `Compile` and - * `crossTarget.value / "scalajs-bundler" / "test"` for `Test`. + * Defaults to `crossTarget.value / "scalajs-bundler" / "main"` for `Compile` and `crossTarget.value / + * "scalajs-bundler" / "test"` for `Test`. */ object ScalaJSBundlerPlugin extends AutoPlugin { override lazy val requires = ScalaJSPlugin // Exported keys - /** - * @groupname tasks Tasks + /** @groupname tasks Tasks * @groupname settings Settings */ object autoImport { @@ -69,37 +65,35 @@ object ScalaJSBundlerPlugin extends AutoPlugin { val BundlerFileTypeAttr: AttributeKey[BundlerFileType] = SBTBundlerFile.BundlerFileTypeAttr implicit class RichBundlerFile(f: BundlerFile.Public) extends SBTBundlerFile(f) - /** - * Installs NPM dependencies and all JavaScript resources found on the classpath as node packages. + /** Installs NPM dependencies and all JavaScript resources found on the classpath as node packages. * - * The JavaScript resources are installed locally in `node_modules` and can be used any other node package, - * such as to load a module using `require()`. + * The JavaScript resources are installed locally in `node_modules` and can be used any other node package, such as + * to load a module using `require()`. * * Do not use this from `sourceGenerators` or any other task that is used either directly or indirectly by * `fullClasspath` otherwise it will result in a deadlock. For this, use [[npmInstallDependencies]] instead. * - * The plugin uses different directories according to the configuration (`Compile` or `Test`). Thus, - * this setting is scoped by a `Configuration`. + * The plugin uses different directories according to the configuration (`Compile` or `Test`). Thus, this setting + * is scoped by a `Configuration`. * - * The task returns the directory in which the dependencies have been fetched (the directory - * that contains the `node_modules` directory). + * The task returns the directory in which the dependencies have been fetched (the directory that contains the + * `node_modules` directory). * * @group tasks */ val npmUpdate: TaskKey[File] = taskKey[File]("Install NPM dependencies and JavaScript resources") - /** - * Installs NPM dependencies. + /** Installs NPM dependencies. * - * Unlike [[npmUpdate]] this does not stage the javascript resources and so is safe to use in `sourceGenerators` - * or any other task that is used by `fullClasspath`. + * Unlike [[npmUpdate]] this does not stage the javascript resources and so is safe to use in `sourceGenerators` or + * any other task that is used by `fullClasspath`. * - * The plugin uses different directories according to the configuration (`Compile` or `Test`). Thus, - * this setting is scoped by a `Configuration`. + * The plugin uses different directories according to the configuration (`Compile` or `Test`). Thus, this setting + * is scoped by a `Configuration`. * - * Typically, if you want to define a task that uses the downloaded NPM packages you should - * specify the `Configuration` you are interested in: + * Typically, if you want to define a task that uses the downloaded NPM packages you should specify the + * `Configuration` you are interested in: * * {{{ * myCustomTask := { @@ -108,24 +102,23 @@ object ScalaJSBundlerPlugin extends AutoPlugin { * } * }}} * - * The task returns the directory in which the dependencies have been fetched (the directory - * that contains the `node_modules` directory). + * The task returns the directory in which the dependencies have been fetched (the directory that contains the + * `node_modules` directory). * * @group tasks */ val npmInstallDependencies: TaskKey[File] = taskKey[File]("Install NPM dependencies") - /** - * Installs all JavaScript resources found on the classpath as node packages. + /** Installs all JavaScript resources found on the classpath as node packages. * * Additionally, it installs also the resources found under [[jsSourceDirectories]] * - * The JavaScript resources are installed locally in `node_modules` and can be used any other node package, - * such as to load a module using `require()`. + * The JavaScript resources are installed locally in `node_modules` and can be used any other node package, such as + * to load a module using `require()`. * - * The plugin uses different directories according to the configuration (`Compile` or `Test`). Thus, - * this setting is scoped by a `Configuration`. + * The plugin uses different directories according to the configuration (`Compile` or `Test`). Thus, this setting + * is scoped by a `Configuration`. * * The task returns the path to each JavaScript resource within the `node_modules` directory. * @@ -134,9 +127,8 @@ object ScalaJSBundlerPlugin extends AutoPlugin { val npmInstallJSResources: TaskKey[Seq[File]] = taskKey[Seq[File]]("Install JavaScript resources found on the classpath") - /** - * List of the NPM packages (name and version) your application depends on. - * You can use [semver](https://docs.npmjs.com/misc/semver) versions: + /** List of the NPM packages (name and version) your application depends on. You can use + * [semver](https://docs.npmjs.com/misc/semver) versions: * * {{{ * npmDependencies in Compile += "uuid" -> "~3.0.0" @@ -153,22 +145,20 @@ object ScalaJSBundlerPlugin extends AutoPlugin { val npmDevDependencies: SettingKey[Seq[(String, String)]] = settingKey[Seq[(String, String)]]("NPM dev dependencies (libraries that the build uses)") - /** - * Map of NPM packages (name -> version) to use in case transitive NPM dependencies - * refer to a same package but with different version numbers. In such a - * case, this setting defines which version should be used for the conflicting - * package. Example: + /** Map of NPM packages (name -> version) to use in case transitive NPM dependencies refer to a same package but + * with different version numbers. In such a case, this setting defines which version should be used for the + * conflicting package. Example: * * {{{ * npmResolutions in Compile := Map("react" -> "15.4.1") * }}} * - * If several Scala.js projects depend on different versions of `react`, the version `15.4.1` - * will be picked. But if all the projects depend on the same version of `react`, the version - * given in `npmResolutions` will be ignored. + * If several Scala.js projects depend on different versions of `react`, the version `15.4.1` will be picked. But + * if all the projects depend on the same version of `react`, the version given in `npmResolutions` will be + * ignored. * - * If different versions of the packages are referred but the package is NOT configured in `npmResolutions`, - * a version conflict resolution is delegated to npm/yarn. This behavior may reduce a need to configure + * If different versions of the packages are referred but the package is NOT configured in `npmResolutions`, a + * version conflict resolution is delegated to npm/yarn. This behavior may reduce a need to configure * `npmResolutions` explicitly. E.g. "14.4.2" can be automatically-picked for ">=14.0.0 14.4.2 ^14.4.1". * * Note that this key must be scoped by a `Configuration` (either `Compile` or `Test`). @@ -178,10 +168,9 @@ object ScalaJSBundlerPlugin extends AutoPlugin { val npmResolutions: SettingKey[Map[String, String]] = settingKey[Map[String, String]]("NPM dependencies resolutions in case of conflict") - /** - * List of the additional configuration options to include in the generated 'package.json'. - * Note that package dependencies are automatically generated from `npmDependencies` and - * `npmDevDependencies` and should '''not''' be specified in this setting. + /** List of the additional configuration options to include in the generated 'package.json'. Note that package + * dependencies are automatically generated from `npmDependencies` and `npmDevDependencies` and should '''not''' be + * specified in this setting. * * {{{ * import scalajsbundler.util.JSON._ @@ -205,8 +194,7 @@ object ScalaJSBundlerPlugin extends AutoPlugin { val additionalNpmConfig: SettingKey[Map[String, JSON]] = settingKey[Map[String, JSON]]("Additional option to include in the generated 'package.json'") - /** - * Additional arguments for npm + /** Additional arguments for npm * * Defaults to an empty list. * @@ -217,24 +205,23 @@ object ScalaJSBundlerPlugin extends AutoPlugin { "Custom arguments for npm" ) - /** - * [[scalajsbundler.BundlingMode]] to use. + /** [[scalajsbundler.BundlingMode]] to use. * - * Must be one of: - * `Application` - Process the entire Scala.js output file with webpack, producing a bundle including all dependencies - * `LibraryOnly()` - Process only the entrypoints via webpack and produce a library of dependencies - * `LibraryAndApplication() - Process only the entrypoints, concatenating the library with the application to produce a bundle + * Must be one of: `Application` - Process the entire Scala.js output file with webpack, producing a bundle + * including all dependencies `LibraryOnly()` - Process only the entrypoints via webpack and produce a library of + * dependencies `LibraryAndApplication() - Process only the entrypoints, concatenating the library with the + * application to produce a bundle * * The default value is `Application` */ val webpackBundlingMode: SettingKey[BundlingMode] = - settingKey[BundlingMode]("Bundling mode, one of BundlingMode.{ Application, LibraryOnly, LibraryAndApplication }.") + settingKey[BundlingMode]( + "Bundling mode, one of BundlingMode.{ Application, LibraryOnly, LibraryAndApplication }.") - /** - * Bundles the output of a Scala.js stage. + /** Bundles the output of a Scala.js stage. * - * This task must be scoped by a Scala.js stage (either `fastOptJS` or `fullOptJS`) and - * a `Configuration` (either `Compile` or `Test`). + * This task must be scoped by a Scala.js stage (either `fastOptJS` or `fullOptJS`) and a `Configuration` (either + * `Compile` or `Test`). * * For instance, to bundle the output of `fastOptJS`, run the following task from the sbt shell: * @@ -248,8 +235,8 @@ object ScalaJSBundlerPlugin extends AutoPlugin { * webpack in (Compile, fastOptJS) * }}} * - * Note that to scope the task to a different project than the “current” sbt project, you - * have to write the following: + * Note that to scope the task to a different project than the “current” sbt project, you have to write the + * following: * * {{{ * webpack in (projectRef, Compile, fastOptJS in projectRef) @@ -257,17 +244,16 @@ object ScalaJSBundlerPlugin extends AutoPlugin { * * The task returns the produced bundles. * - * The task is cached, so webpack is only launched when some of the - * used files have changed. The list of files to be monitored is - * provided by webpackMonitoredFiles + * The task is cached, so webpack is only launched when some of the used files have changed. The list of files to + * be monitored is provided by webpackMonitoredFiles * * The files produced are wrapped in `sbt.Attributed`, and tagged with * [[scalajsbundler.sbtplugin.SBTBundlerFile.ProjectNameAttr]] and * [[scalajsbundler.sbtplugin.SBTBundlerFile.BundlerFileTypeAttr]]. The - * [[scalajsbundler.sbtplugin.SBTBundlerFile.ProjectNameAttr]] contains the "prefix" of the file names, such - * as `yourapp-fastopt`, while the - * [[scalajsbundler.sbtplugin.SBTBundlerFile.BundlerFileTypeAttr]] contains the bundle file type, which can be - * used to filter the list of files by their [[scalajsbundler.BundlerFileType]]. For example: + * [[scalajsbundler.sbtplugin.SBTBundlerFile.ProjectNameAttr]] contains the "prefix" of the file names, such as + * `yourapp-fastopt`, while the [[scalajsbundler.sbtplugin.SBTBundlerFile.BundlerFileTypeAttr]] contains the bundle + * file type, which can be used to filter the list of files by their [[scalajsbundler.BundlerFileType]]. For + * example: * * {{{ * webpack.value.find(_.metadata.get(BundlerFileTypeAttr).exists(_ == BundlerFileType.ApplicationBundle)) @@ -278,9 +264,8 @@ object ScalaJSBundlerPlugin extends AutoPlugin { val webpack: TaskKey[Seq[Attributed[File]]] = taskKey[Seq[Attributed[File]]]("Bundle the output of a Scala.js stage using webpack") - /** - * configuration file to use with webpack. By default, the plugin generates a - * configuration file, but you can supply your own file via this setting. Example of use: + /** configuration file to use with webpack. By default, the plugin generates a configuration file, but you can + * supply your own file via this setting. Example of use: * * {{{ * webpackConfigFile in fullOptJS := Some(baseDirectory.value / "my.dev.webpack.config.js") @@ -294,8 +279,7 @@ object ScalaJSBundlerPlugin extends AutoPlugin { val webpackConfigFile: SettingKey[Option[File]] = settingKey[Option[File]]("Configuration file to use with webpack") - /** - * Webpack configuration files to copy to the target directory. These files can be merged into the main + /** Webpack configuration files to copy to the target directory. These files can be merged into the main * configuration file. * * By default all .js files in the project base directory are copied: @@ -312,18 +296,15 @@ object ScalaJSBundlerPlugin extends AutoPlugin { val webpackResources: SettingKey[PathFinder] = settingKey[PathFinder]("Webpack resources to copy to target directory (defaults to *.js)") - /** - * whether to enable (or not) source-map in - * a given configuration (`Compile` or `Test`) and stage (`fastOptJS` or `fullOptJS`). Example - * of use: + /** whether to enable (or not) source-map in a given configuration (`Compile` or `Test`) and stage (`fastOptJS` or + * `fullOptJS`). Example of use: * * {{{ * webpackEmitSourceMaps in (Compile, fullOptJS) := false * }}} * - * By default, this setting is undefined and scalajs-bundler fallbacks to Scala.js’ `sourceMap` - * setting, so, to globally disable source maps you can just configure the `sourceMap` - * setting: + * By default, this setting is undefined and scalajs-bundler fallbacks to Scala.js’ `sourceMap` setting, so, to + * globally disable source maps you can just configure the `sourceMap` setting: * * {{{ * scalaJSLinkerConfig ~= _.withSourceMap(false) @@ -337,41 +318,39 @@ object ScalaJSBundlerPlugin extends AutoPlugin { private[scalajsbundler] val finallyEmitSourceMaps: SettingKey[Boolean] = SettingKey("finallyEmitSourceMaps", rank = KeyRanks.Invisible) - /** - * Additional directories, monitored for webpack launch. + /** Additional directories, monitored for webpack launch. * - * Changes to files in these directories that match - * `includeFilter` scoped to `webpackMonitoredFiles` enable - * webpack launch in `webpack` task. + * Changes to files in these directories that match `includeFilter` scoped to `webpackMonitoredFiles` enable + * webpack launch in `webpack` task. * * Defaults to an empty `Seq`. * * @group settings - * @see [[webpackMonitoredFiles]] + * @see + * [[webpackMonitoredFiles]] */ val webpackMonitoredDirectories: SettingKey[Seq[File]] = settingKey[Seq[File]]("Directories, monitored for webpack launch") - /** - * List of files, monitored for webpack launch. + /** List of files, monitored for webpack launch. * * By default includes the following files: - * - Generated `package.json` - * - Generated webpack config - * - Custom webpack config (if any) - * - Files, provided by `webpackEntries` task. - * - Files from `webpackMonitoredDirectories`, filtered by - * `includeFilter` + * - Generated `package.json` + * - Generated webpack config + * - Custom webpack config (if any) + * - Files, provided by `webpackEntries` task. + * - Files from `webpackMonitoredDirectories`, filtered by `includeFilter` * * @group settings - * @see [[webpackMonitoredDirectories]] - * @see [[webpack]] + * @see + * [[webpackMonitoredDirectories]] + * @see + * [[webpack]] */ val webpackMonitoredFiles: TaskKey[Seq[File]] = taskKey[Seq[File]]("Files that trigger webpack launch") - /** - * Additional arguments to webpack + /** Additional arguments to webpack * * Defaults to an empty list. * @@ -382,8 +361,7 @@ object ScalaJSBundlerPlugin extends AutoPlugin { "Custom arguments to webpack" ) - /** - * node.js cli custom arguments as described in https://nodejs.org/api/cli.html + /** node.js cli custom arguments as described in https://nodejs.org/api/cli.html * * Defaults to an empty list. * @@ -394,12 +372,10 @@ object ScalaJSBundlerPlugin extends AutoPlugin { "Custom arguments to node.js when running webpack tasks" ) - /** - * Whether to use [[https://yarnpkg.com/ Yarn]] to fetch dependencies instead - * of `npm`. Yarn has a caching mechanism that makes the process faster. + /** Whether to use [[https://yarnpkg.com/ Yarn]] to fetch dependencies instead of `npm`. Yarn has a caching + * mechanism that makes the process faster. * - * If set to `true`, it requires Yarn 0.22.0+ to be available on the - * host platform. + * If set to `true`, it requires Yarn 0.22.0+ to be available on the host platform. * * Defaults to `false`. * @@ -408,12 +384,12 @@ object ScalaJSBundlerPlugin extends AutoPlugin { val useYarn: SettingKey[Boolean] = settingKey[Boolean]("Whether to use yarn for updates") - /** - * Port, on which webpack-dev-server will be launched. + /** Port, on which webpack-dev-server will be launched. * * Defaults to 8080. * - * @see [[startWebpackDevServer]] + * @see + * [[startWebpackDevServer]] * @group settings */ val webpackDevServerPort = SettingKey[Int]( @@ -421,8 +397,7 @@ object ScalaJSBundlerPlugin extends AutoPlugin { "Port, on which webpack-dev-server operates" ) - /** - * Additional arguments for yarn + /** Additional arguments for yarn * * Defaults to an empty list. * @@ -432,12 +407,13 @@ object ScalaJSBundlerPlugin extends AutoPlugin { "yarnExtraArgs", "Custom arguments for yarn" ) - /** - * Additional arguments to webpack-dev-server. + + /** Additional arguments to webpack-dev-server. * * Defaults to an empty list. * - * @see [[startWebpackDevServer]] + * @see + * [[startWebpackDevServer]] * @group settings */ val webpackDevServerExtraArgs = SettingKey[Seq[String]]( @@ -445,27 +421,28 @@ object ScalaJSBundlerPlugin extends AutoPlugin { "Custom arguments to webpack-dev-server" ) - /** - * Version of webpack-cli + /** Version of webpack-cli * * @group settings */ val webpackCliVersion: SettingKey[String] = settingKey[String]("Version of webpack-cli to use") - /** - * Start background webpack-dev-server process. + /** Start background webpack-dev-server process. * * If webpack-dev-server is already running, it will be restarted. * * The started webpack-dev-server receives the following arguments: - * - `--config` is set to value of `webpackConfigFile` setting. - * - `--port` is set to value of `webpackDevServerPort` setting. - * - Contents of `webpackDevServerExtraArgs` setting. - * - * @see [[stopWebpackDevServer]] - * @see [[webpackDevServerPort]] - * @see [[webpackDevServerExtraArgs]] + * - `--config` is set to value of `webpackConfigFile` setting. + * - `--port` is set to value of `webpackDevServerPort` setting. + * - Contents of `webpackDevServerExtraArgs` setting. + * + * @see + * [[stopWebpackDevServer]] + * @see + * [[webpackDevServerPort]] + * @see + * [[webpackDevServerExtraArgs]] * @group tasks */ val startWebpackDevServer = TaskKey[Unit]( @@ -473,12 +450,12 @@ object ScalaJSBundlerPlugin extends AutoPlugin { "(Re)start webpack-dev-server process" ) - /** - * Stop running webpack-dev-server process. + /** Stop running webpack-dev-server process. * * Does nothing if the server is not running. * - * @see [[startWebpackDevServer]] + * @see + * [[startWebpackDevServer]] * @group tasks */ val stopWebpackDevServer = TaskKey[Unit]( @@ -486,8 +463,7 @@ object ScalaJSBundlerPlugin extends AutoPlugin { "Stop webpack-dev-server process (if running)" ) - /** - * Locally install jsdom. + /** Locally install jsdom. * * You can set the jsdom package version to install with the key `version in installJsdom`. * @@ -497,8 +473,7 @@ object ScalaJSBundlerPlugin extends AutoPlugin { */ val installJsdom = taskKey[File]("Locally install jsdom") - /** - * A flag to indicate the need to use a DOM enabled JS environment in test. + /** A flag to indicate the need to use a DOM enabled JS environment in test. * * Default is false. * @@ -506,8 +481,7 @@ object ScalaJSBundlerPlugin extends AutoPlugin { */ val requireJsDomEnv = taskKey[Boolean]("Require DOM enabled environment in test") - /** - * Local js source directories to be collected by the bundler + /** Local js source directories to be collected by the bundler * * Default is `src/main/js` * @@ -517,22 +491,19 @@ object ScalaJSBundlerPlugin extends AutoPlugin { } private[sbtplugin] val scalaJSBundlerImportedModules = - TaskKey[List[String]]("scalaJSBundlerImportedModules", - "Computes the list of imported modules", - KeyRanks.Invisible - ) + TaskKey[List[String]]("scalaJSBundlerImportedModules", "Computes the list of imported modules", KeyRanks.Invisible) private val scalaJSBundlerPackageJson = - TaskKey[BundlerFile.PackageJson]("scalaJSBundlerPackageJson", + TaskKey[BundlerFile.PackageJson]( + "scalaJSBundlerPackageJson", "Write a package.json file defining the NPM dependencies of project", - KeyRanks.Invisible - ) + KeyRanks.Invisible) private[sbtplugin] val scalaJSBundlerWebpackConfig = - TaskKey[BundlerFile.WebpackConfig]("scalaJSBundlerWebpackConfig", + TaskKey[BundlerFile.WebpackConfig]( + "scalaJSBundlerWebpackConfig", "Write the webpack configuration file", - KeyRanks.Invisible - ) + KeyRanks.Invisible) private val webpackDevServer = SettingKey[WebpackDevServer]( "webpackDevServer", @@ -550,31 +521,23 @@ object ScalaJSBundlerPlugin extends AutoPlugin { import autoImport.{BundlerFile => _, _} override lazy val projectSettings: Seq[Def.Setting[_]] = Seq( - scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }, - version in webpack := "5.24.3", - webpackCliVersion := "4.5.0", - version in startWebpackDevServer := "3.11.2", - version in installJsdom := "9.9.0", - webpackConfigFile := None, - webpackResources := baseDirectory.value * "*.js", // Include the manifest in the produced artifact (products in Compile) := (products in Compile).dependsOn(scalaJSBundlerManifest).value, - useYarn := false, - ensureModuleKindIsCommonJSModule := { if (scalaJSLinkerConfig.value.moduleKind == ModuleKind.CommonJSModule) true - else sys.error(s"scalaJSModuleKind must be set to ModuleKind.CommonJSModule in projects where ScalaJSBundler plugin is enabled") + else + sys.error( + s"scalaJSModuleKind must be set to ModuleKind.CommonJSModule in projects where ScalaJSBundler plugin is enabled") }, - webpackBundlingMode := BundlingMode.Default, // Make these settings project-level, since we don't expect much @@ -584,7 +547,6 @@ object ScalaJSBundlerPlugin extends AutoPlugin { (includeFilter in webpackMonitoredFiles) := AllPassFilter, webpackExtraArgs := Seq.empty, webpackNodeArgs := Seq.empty, - npmExtraArgs := Seq.empty, yarnExtraArgs := Seq.empty, @@ -594,7 +556,6 @@ object ScalaJSBundlerPlugin extends AutoPlugin { // We can only have one server per project - for simplicity webpackDevServer := new WebpackDevServer(), - (onLoad in Global) := { (onLoad in Global).value.compose( _.addExitHook { @@ -602,7 +563,6 @@ object ScalaJSBundlerPlugin extends AutoPlugin { } ) }, - installJsdom := { val installDir = target.value / "scalajs-bundler-jsdom" val baseDir = baseDirectory.value @@ -612,7 +572,8 @@ object ScalaJSBundlerPlugin extends AutoPlugin { if (!jsdomDir.exists()) { log.info(s"Installing jsdom in ${installDir.absolutePath}") IO.createDirectory(installDir) - addPackages(baseDir, installDir, useYarn.value, log, npmExtraArgs.value, yarnExtraArgs.value)(s"jsdom@$jsdomVersion") + addPackages(baseDir, installDir, useYarn.value, log, npmExtraArgs.value, yarnExtraArgs.value)( + s"jsdom@$jsdomVersion") } installDir } @@ -623,23 +584,17 @@ object ScalaJSBundlerPlugin extends AutoPlugin { private lazy val perConfigSettings: Seq[Def.Setting[_]] = Def.settings( npmDependencies := Seq.empty, - npmDevDependencies := Seq.empty, - npmResolutions := Map.empty, - additionalNpmConfig := Map( "private" -> JSON.bool(true), "license" -> JSON.str("UNLICENSED") ), - - jsSourceDirectories := Seq(sourceDirectory.value / "js"), - + jsSourceDirectories := Seq(sourceDirectory.value / "js"), npmUpdate := { val _ = npmInstallJSResources.value npmInstallDependencies.value }, - npmInstallDependencies := NpmUpdateTasks.npmInstallDependencies( baseDirectory.value, (crossTarget in npmUpdate).value, @@ -647,14 +602,13 @@ object ScalaJSBundlerPlugin extends AutoPlugin { useYarn.value, streams.value, npmExtraArgs.value, - yarnExtraArgs.value), - + yarnExtraArgs.value + ), npmInstallJSResources := NpmUpdateTasks.npmInstallJSResources( (crossTarget in npmUpdate).value, ScalaJSNativeLibraries(fullClasspath.value), jsSourceDirectories.value, streams.value), - scalaJSBundlerPackageJson := PackageJsonTasks.writePackageJson( (crossTarget in npmUpdate).value, @@ -669,16 +623,13 @@ object ScalaJSBundlerPlugin extends AutoPlugin { webpackCliVersion.value, streams.value ), - - crossTarget in npmUpdate := { crossTarget.value / "scalajs-bundler" / (if (configuration.value == Compile) "main" else "test") }, - Settings.configSettings ) ++ - perScalaJSStageSettings(Stage.FastOpt) ++ - perScalaJSStageSettings(Stage.FullOpt) + perScalaJSStageSettings(Stage.FastOpt) ++ + perScalaJSStageSettings(Stage.FullOpt) override def globalSettings: Seq[Def.Setting[_]] = Settings.globalSettings @@ -686,15 +637,10 @@ object ScalaJSBundlerPlugin extends AutoPlugin { private lazy val testSettings: Seq[Setting[_]] = Def.settings( npmDependencies ++= (npmDependencies in Compile).value, - npmDevDependencies ++= (npmDevDependencies in Compile).value, - jsSourceDirectories ++= (jsSourceDirectories in Compile).value, - requireJsDomEnv := false, - Settings.testConfigSettings - ) private def perScalaJSStageSettings(stage: Stage): Seq[Def.Setting[_]] = { @@ -707,7 +653,6 @@ object ScalaJSBundlerPlugin extends AutoPlugin { Seq( // Ask Scala.js to output its result in our target directory crossTarget in stageTask := (crossTarget in npmUpdate).value, - finallyEmitSourceMaps in stageTask := { (webpackEmitSourceMaps in stageTask).?.value .getOrElse((scalaJSLinkerConfig in stageTask).value.sourceMap) @@ -724,12 +669,10 @@ object ScalaJSBundlerPlugin extends AutoPlugin { prev.withRelativizeSourceMapBase(Some(relSourceMapBase)) } }, - scalaJSBundlerWebpackConfig in stageTask := BundlerFile.WebpackConfig( WebpackTasks.entry(stageTask).value, npmUpdate.value / "scalajs.webpack.config.js" ), - webpack in stageTask := Def.taskDyn { (webpackBundlingMode in stageTask).value match { case scalajsbundler.BundlingMode.Application => @@ -740,7 +683,6 @@ object ScalaJSBundlerPlugin extends AutoPlugin { LibraryTasks.libraryAndLoadersBundle(stageTask, mode) } }.value, - webpackMonitoredFiles in stageTask := { val generatedWebpackConfigFile = (scalaJSBundlerWebpackConfig in stageTask).value val customWebpackConfigFile = (webpackConfigFile in stageTask).value @@ -749,13 +691,8 @@ object ScalaJSBundlerPlugin extends AutoPlugin { val filter = (includeFilter in webpackMonitoredFiles).value val dirs = (webpackMonitoredDirectories in stageTask).value - val generatedFiles: Seq[File] = Seq( - packageJsonFile.file, - generatedWebpackConfigFile.file, - entry.file) - val additionalFiles: Seq[File] = dirs.flatMap( - dir => (dir ** filter).get - ) + val generatedFiles: Seq[File] = Seq(packageJsonFile.file, generatedWebpackConfigFile.file, entry.file) + val additionalFiles: Seq[File] = dirs.flatMap(dir => (dir ** filter).get) generatedFiles ++ customWebpackConfigFile.toSeq ++ webpackResources.value.get ++ @@ -763,40 +700,42 @@ object ScalaJSBundlerPlugin extends AutoPlugin { }, // webpack-dev-server wiring - startWebpackDevServer in stageTask := Def.task { - val extraArgs = (webpackDevServerExtraArgs in stageTask).value - - // This duplicates file layout logic from `Webpack` - val targetDir = (npmUpdate in stageTask).value - val customConfigOption = (webpackConfigFile in stageTask).value - val generatedConfig = (scalaJSBundlerWebpackConfig in stageTask).value - - val config = customConfigOption - .map(Webpack.copyCustomWebpackConfigFiles(targetDir, webpackResources.value.get)) - .getOrElse(generatedConfig.file) - - // To match `webpack` task behavior - val workDir = targetDir - - // Server instance is project-level - val server = webpackDevServer.value - val logger = (streams in stageTask).value.log - val globalLogger = state.value.globalLogging.full - - server.start( - workDir, - config, - extraArgs, - logger, - globalLogger + startWebpackDevServer in stageTask := Def + .task { + val extraArgs = (webpackDevServerExtraArgs in stageTask).value + + // This duplicates file layout logic from `Webpack` + val targetDir = (npmUpdate in stageTask).value + val customConfigOption = (webpackConfigFile in stageTask).value + val generatedConfig = (scalaJSBundlerWebpackConfig in stageTask).value + + val config = customConfigOption + .map(Webpack.copyCustomWebpackConfigFiles(targetDir, webpackResources.value.get)) + .getOrElse(generatedConfig.file) + + // To match `webpack` task behavior + val workDir = targetDir + + // Server instance is project-level + val server = webpackDevServer.value + val logger = (streams in stageTask).value.log + val globalLogger = state.value.globalLogging.full + + server.start( + workDir, + config, + extraArgs, + logger, + globalLogger + ) + } + .dependsOn( + // We need to execute the full webpack task once, since it generates + // the required config file + (webpack in stageTask), + npmUpdate ) - }.dependsOn( - // We need to execute the full webpack task once, since it generates - // the required config file - (webpack in stageTask), - - npmUpdate - ).value, + .value, // Stops the global server instance, but is defined on stage // level to match `startWebpackDevServer` @@ -806,9 +745,7 @@ object ScalaJSBundlerPlugin extends AutoPlugin { ) } - /** - * Writes the scalajs-bundler manifest file. - */ + /** Writes the scalajs-bundler manifest file. */ val scalaJSBundlerManifest: Def.Initialize[Task[File]] = Def.task { NpmDependencies.writeManifest( diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/Settings.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/Settings.scala index 1a92bdf5..ecd2312c 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/Settings.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/Settings.scala @@ -10,24 +10,33 @@ import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ import sbt.Keys._ import sbt._ import scalajsbundler.{JSDOMNodeJSEnv, Webpack, JsDomTestEntries, NpmPackage} -import scalajsbundler.sbtplugin.ScalaJSBundlerPlugin.autoImport.{installJsdom, npmUpdate, requireJsDomEnv, webpackConfigFile, webpackNodeArgs, webpackResources, webpack} +import scalajsbundler.sbtplugin.ScalaJSBundlerPlugin.autoImport.{ + installJsdom, + npmUpdate, + requireJsDomEnv, + webpackConfigFile, + webpackNodeArgs, + webpackResources, + webpack +} import scalajsbundler.sbtplugin.ScalaJSBundlerPlugin.{ensureModuleKindIsCommonJSModule, scalaJSBundlerImportedModules} import scalajsbundler.sbtplugin.internal.BuildInfo private[sbtplugin] object Settings { - private class BundlerLinkerImpl(base: LinkerImpl.Reflect) - extends LinkerImpl.Forwarding(base) { + private class BundlerLinkerImpl(base: LinkerImpl.Reflect) extends LinkerImpl.Forwarding(base) { private val loader = base.loader private val clearableLinkerMethod = { - Class.forName("scalajsbundler.bundlerlinker.BundlerLinkerImpl", true, loader) + Class + .forName("scalajsbundler.bundlerlinker.BundlerLinkerImpl", true, loader) .getMethod("clearableLinker", classOf[StandardConfig], classOf[Path]) } def bundlerLinker(config: StandardConfig, entryPointOutputFile: Path): ClearableLinker = { - clearableLinkerMethod.invoke(null, config, entryPointOutputFile) + clearableLinkerMethod + .invoke(null, config, entryPointOutputFile) .asInstanceOf[ClearableLinker] } } @@ -43,16 +52,15 @@ private[sbtplugin] object Settings { val dummyModuleID = "ch.epfl.scala" % "scalajs-bundler-linker-and-scalajs-linker_2.12" % s"${BuildInfo.version}/$scalaJSVersion" val dependencies = Vector( - // Load our linker back-end - "ch.epfl.scala" % "scalajs-bundler-linker_2.12" % BuildInfo.version, - // And force-bump the dependency on scalajs-linker to match the version of sbt-scalajs - "org.scala-js" % "scalajs-linker_2.12" % scalaJSVersion + // Load our linker back-end + "ch.epfl.scala" % "scalajs-bundler-linker_2.12" % BuildInfo.version, + // And force-bump the dependency on scalajs-linker to match the version of sbt-scalajs + "org.scala-js" % "scalajs-linker_2.12" % scalaJSVersion ) val moduleDescriptor = lm.moduleDescriptor(dummyModuleID, dependencies, scalaModuleInfo = None) lm.retrieve(moduleDescriptor, retrieveDir, log) .fold(w => throw w.resolveException, Attributed.blankSeq(_)) }, - scalaJSLinkerImpl := { val cp = (fullClasspath in scalaJSLinkerImpl).value scalaJSLinkerImplBox.value.ensure { @@ -62,7 +70,9 @@ private[sbtplugin] object Settings { ) // Settings that must be applied for each stage in each configuration - private def scalaJSStageSettings(stage: Stage, key: TaskKey[Attributed[Report]], + private def scalaJSStageSettings( + stage: Stage, + key: TaskKey[Attributed[Report]], legacyKey: TaskKey[Attributed[File]]): Seq[Setting[_]] = { val entryPointOutputFileName = s"entrypoints-${stage.toString.toLowerCase}.txt" @@ -75,11 +85,11 @@ private[sbtplugin] object Settings { val entryPointOutputFile = crossTarget.value / entryPointOutputFileName box.ensure { - linkerImpl.asInstanceOf[BundlerLinkerImpl] + linkerImpl + .asInstanceOf[BundlerLinkerImpl] .bundlerLinker(config, entryPointOutputFile.toPath) } }, - scalaJSBundlerImportedModules in legacyKey := { val _ = legacyKey.value val entryPointOutputFile = crossTarget.value / entryPointOutputFileName @@ -109,9 +119,8 @@ private[sbtplugin] object Settings { val optMainModule = report.publicModules.find(_.moduleID == "main") val optMainModulePath = optMainModule.map { mainModule => val linkerOutputDirectory = linkingResult.get(scalaJSLinkerOutputDirectory.key).getOrElse { - throw new MessageOnlyException( - "Linking report was not attributed with output directory. " + - "Please report this as a Scala.js bug.") + throw new MessageOnlyException("Linking report was not attributed with output directory. " + + "Please report this as a Scala.js bug.") } (linkerOutputDirectory / mainModule.jsFileName).toPath } @@ -129,7 +138,6 @@ private[sbtplugin] object Settings { prev } }, - scalaJSStageSettings(FastOptStage, fastLinkJS, fastOptJS), scalaJSStageSettings(FullOptStage, fullLinkJS, fullOptJS) ) @@ -140,12 +148,14 @@ private[sbtplugin] object Settings { // Configure a JSDOMNodeJSEnv with an installation of jsdom if requireJsDomEnv is true jsEnv := { val defaultJSEnv = jsEnv.value - val optJsdomDir = Def.taskDyn[Option[File]] { - if (requireJsDomEnv.value) - installJsdom.map(Some(_)) - else - Def.task(None) - }.value + val optJsdomDir = Def + .taskDyn[Option[File]] { + if (requireJsDomEnv.value) + installJsdom.map(Some(_)) + else + Def.task(None) + } + .value optJsdomDir match { case Some(jsdomDir) => new JSDOMNodeJSEnv(JSDOMNodeJSEnv.Config(jsdomDir)) case None => defaultJSEnv @@ -153,83 +163,95 @@ private[sbtplugin] object Settings { }, // Use the product of bundling in jsEnvInput if requireJsDomEnv is true - jsEnvInput := Def.task { - assert(ensureModuleKindIsCommonJSModule.value) - val prev = jsEnvInput.value - val sjsOutput = scalaJSLinkedFile.value.data - - val optBundle = { - Def.taskDyn[Option[org.scalajs.jsenv.Input]] { - val sjsOutput = scalaJSLinkedFile.value.data - // If jsdom is going to be used, then we should bundle the test module - if (requireJsDomEnv.value) Def.task { - val logger = streams.value.log - val targetDir = npmUpdate.value - val sjsOutputName = sjsOutput.name.stripSuffix(".js") - val bundle = targetDir / s"$sjsOutputName-bundle.js" - val webpackVersion = (version in webpack).value - - val customWebpackConfigFile = (webpackConfigFile in Test).value - val nodeArgs = (webpackNodeArgs in Test).value - - val writeTestBundleFunction = - FileFunction.cached( - streams.value.cacheDirectory / "test-loader", - inStyle = FilesInfo.hash - ) { _ => - logger.info("Writing and bundling the test loader") - val loader = targetDir / s"$sjsOutputName-loader.js" - JsDomTestEntries.writeLoader(sjsOutput, loader) - - val configArgs = customWebpackConfigFile match { - case Some(configFile) => - val customConfigFileCopy = Webpack.copyCustomWebpackConfigFiles(targetDir, webpackResources.value.get)(configFile) - Seq("--config", customConfigFileCopy.getAbsolutePath) - - case None => - Seq.empty - } + jsEnvInput := Def + .task { + assert(ensureModuleKindIsCommonJSModule.value) + val prev = jsEnvInput.value + val sjsOutput = scalaJSLinkedFile.value.data - // TODO: It assumes tests are run on development mode. It should instead use build settings - val allArgs = Seq( - "--mode", "development", - "--entry", loader.absolutePath, - "--output-path", bundle.getParentFile.absolutePath, - "--output-filename", bundle.name - ) ++ configArgs - - NpmPackage(webpackVersion).major match { - case Some(5) => - Webpack.run(nodeArgs: _*)(allArgs: _*)(targetDir, logger) - case Some(x) => - sys.error(s"Unsupported webpack major version $x") - case None => - sys.error("No webpack version defined") - } + val optBundle = { + Def + .taskDyn[Option[org.scalajs.jsenv.Input]] { + val sjsOutput = scalaJSLinkedFile.value.data + // If jsdom is going to be used, then we should bundle the test module + if (requireJsDomEnv.value) Def.task { + val logger = streams.value.log + val targetDir = npmUpdate.value + val sjsOutputName = sjsOutput.name.stripSuffix(".js") + val bundle = targetDir / s"$sjsOutputName-bundle.js" + val webpackVersion = (version in webpack).value - Set.empty - } - writeTestBundleFunction(Set(sjsOutput)) + val customWebpackConfigFile = (webpackConfigFile in Test).value + val nodeArgs = (webpackNodeArgs in Test).value - Some(Script(bundle.toPath)) - } else Def.task { - None - } - }.value - } + val writeTestBundleFunction = + FileFunction.cached( + streams.value.cacheDirectory / "test-loader", + inStyle = FilesInfo.hash + ) { _ => + logger.info("Writing and bundling the test loader") + val loader = targetDir / s"$sjsOutputName-loader.js" + JsDomTestEntries.writeLoader(sjsOutput, loader) - optBundle match { - case Some(bundle) => - prev.map { - case CommonJSModule(module) if module == sjsOutput.toPath() => - bundle - case inputItem => - inputItem - } - case None => - prev + val configArgs = customWebpackConfigFile match { + case Some(configFile) => + val customConfigFileCopy = + Webpack.copyCustomWebpackConfigFiles(targetDir, webpackResources.value.get)(configFile) + Seq("--config", customConfigFileCopy.getAbsolutePath) + + case None => + Seq.empty + } + + // TODO: It assumes tests are run on development mode. It should instead use build settings + val allArgs = Seq( + "--mode", + "development", + "--entry", + loader.absolutePath, + "--output-path", + bundle.getParentFile.absolutePath, + "--output-filename", + bundle.name + ) ++ configArgs + + NpmPackage(webpackVersion).major match { + case Some(5) => + Webpack.run(nodeArgs: _*)(allArgs: _*)(targetDir, logger) + case Some(x) => + sys.error(s"Unsupported webpack major version $x") + case None => + sys.error("No webpack version defined") + } + + Set.empty + } + writeTestBundleFunction(Set(sjsOutput)) + + Some(Script(bundle.toPath)) + } + else + Def.task { + None + } + } + .value + } + + optBundle match { + case Some(bundle) => + prev.map { + case CommonJSModule(module) if module == sjsOutput.toPath() => + bundle + case inputItem => + inputItem + } + case None => + prev + } } - }.dependsOn(npmUpdate).value + .dependsOn(npmUpdate) + .value ) } diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/WebpackTasks.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/WebpackTasks.scala index 665589a4..10ff1cbf 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/WebpackTasks.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/WebpackTasks.scala @@ -1,4 +1,5 @@ package scalajsbundler.sbtplugin + import sbt.Keys._ import sbt.{Def, _} @@ -8,15 +9,14 @@ import scalajsbundler.sbtplugin.ScalaJSBundlerPlugin.autoImport._ import scalajsbundler.Webpack object WebpackTasks { - private[sbtplugin] def entry(stage: TaskKey[Attributed[File]]) - : Def.Initialize[Task[BundlerFile.Application]] = + + private[sbtplugin] def entry(stage: TaskKey[Attributed[File]]): Def.Initialize[Task[BundlerFile.Application]] = Def.task { val projectName = stage.value.data.name.stripSuffix(".js") BundlerFile.Application(projectName, stage.value.data, Nil) } - private[sbtplugin] def webpack( - stage: TaskKey[Attributed[File]]): Def.Initialize[Task[Seq[Attributed[File]]]] = + private[sbtplugin] def webpack(stage: TaskKey[Attributed[File]]): Def.Initialize[Task[Seq[Attributed[File]]]] = Def.task { assert(ensureModuleKindIsCommonJSModule.value) val cacheLocation = streams.value.cacheDirectory / s"${stage.key.label}-webpack" @@ -40,19 +40,21 @@ object WebpackTasks { cacheLocation, inStyle = FilesInfo.hash ) { _ => - Webpack.bundle( - emitSourceMaps, - generatedWebpackConfigFile, - customWebpackConfigFile, - webpackResourceFiles, - entriesList, - targetDir, - extraArgs, - nodeArgs, - webpackMode, - devServerPort, - log - ).cached + Webpack + .bundle( + emitSourceMaps, + generatedWebpackConfigFile, + customWebpackConfigFile, + webpackResourceFiles, + entriesList, + targetDir, + extraArgs, + nodeArgs, + webpackMode, + devServerPort, + log + ) + .cached } val cached = cachedActionFunction(monitoredFiles.to[Set]) generatedWebpackConfigFile.asApplicationBundleFromCached(cached).asAttributedFiles diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/CachedBundleFiles.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/CachedBundleFiles.scala index fede1609..11973214 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/CachedBundleFiles.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/CachedBundleFiles.scala @@ -4,6 +4,7 @@ import java.io.File import scala.collection.immutable.ListSet object CachedBundleFiles { + def cached(file: File, assets: List[File]): ListSet[File] = { // Let's put the main file first val sortedAssets = file :: assets.filterNot(_ == file) diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/Commands.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/Commands.scala index 81edd57b..70674b44 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/Commands.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/Commands.scala @@ -8,7 +8,11 @@ import scala.sys.process.ProcessLogger object Commands { - def run[A](cmd: Seq[String], cwd: File, logger: Logger, outputProcess: InputStream => A): Either[String, Option[A]] = { + def run[A]( + cmd: Seq[String], + cwd: File, + logger: Logger, + outputProcess: InputStream => A): Either[String, Option[A]] = { val toErrorLog = (is: InputStream) => { scala.io.Source.fromInputStream(is).getLines.foreach(msg => logger.error(msg)) is.close() diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/JS.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/JS.scala index 29ed95fd..828a88bc 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/JS.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/JS.scala @@ -11,6 +11,7 @@ private[util] sealed abstract class JSLike(val tree: Tree) { } object JSLike { + private def show(tree: Tree, isStat: Boolean): String = { val writer = new java.io.StringWriter val printer = new JSPrinters.JSTreePrinter(writer) @@ -20,7 +21,7 @@ object JSLike { } /** A convenient wrapper around JS trees */ -final class JS private(tree: Tree) extends JSLike(tree) { +final class JS private (tree: Tree) extends JSLike(tree) { def dot(ident: String): JS = JS(DotSelect(tree, Ident(ident))) def bracket(ident: String): JS = JS(BracketSelect(tree, StringLiteral(ident))) def bracket(ident: JSLike): JS = JS(BracketSelect(tree, ident.tree)) @@ -96,12 +97,13 @@ object JS { def `new`(ctor: JS, args: JSLike*): JS = JS(New(ctor.tree, args.map(_.tree).to[List])) private val identifierSeq = new AtomicInteger(0) + private def freshIdentifier(): String = s"x${identifierSeq.getAndIncrement()}" } -final class JSON private(tree: Tree) extends JSLike(tree) +final class JSON private (tree: Tree) extends JSLike(tree) object JSON { diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/JSBundler.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/JSBundler.scala index 623692d5..af6e2cdb 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/JSBundler.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/JSBundler.scala @@ -18,12 +18,14 @@ object JSBundler { ): Unit = IO.write(loaderFile.file, loaderScript(bundleName)) - /** - * Run webpack to bundle the application. + /** Run webpack to bundle the application. * - * @param targetDir Target directory (and working directory for Nodejs) - * @param logger Logger - * @return The generated bundles + * @param targetDir + * Target directory (and working directory for Nodejs) + * @param logger + * Logger + * @return + * The generated bundles */ def bundle( targetDir: File, @@ -43,27 +45,23 @@ object JSBundler { JS.ref("require")(JS.str("concat-with-sourcemaps")), JS.ref("require")(JS.str("fs")) ) { (Concat, fs) => - JS.let( - JS.`new`(Concat, - JS.bool(true), - JS.str(bundleFile.file.name), - JS.str(";\n"))) { concat => + JS.let(JS.`new`(Concat, JS.bool(true), JS.str(bundleFile.file.name), JS.str(";\n"))) { concat => JS.block( concat .dot("add") .apply( JS.str(""), fs.dot("readFileSync") - .apply(JS.str(libraryFile.file.absolutePath), - JS.str("utf-8")), + .apply(JS.str(libraryFile.file.absolutePath), JS.str("utf-8")), fs.dot("readFileSync") .apply(JS.str(libraryFile.file.absolutePath ++ ".map")) ), concat .dot("add") - .apply(JS.str(loaderFile.file.name), - fs.dot("readFileSync") - .apply(JS.str(loaderFile.file.absolutePath))), + .apply( + JS.str(loaderFile.file.name), + fs.dot("readFileSync") + .apply(JS.str(loaderFile.file.absolutePath))), concat .dot("add") .apply( @@ -71,24 +69,22 @@ object JSBundler { fs.dot("readFileSync") .apply(JS.str(entry.file.absolutePath), JS.str("utf-8")), fs.dot("readFileSync") - .apply(JS.str(entry.file.absolutePath ++ ".map"), - JS.str("utf-8")) + .apply(JS.str(entry.file.absolutePath ++ ".map"), JS.str("utf-8")) ), - JS.let(JS.ref("Buffer").dot("from").apply( - JS.str(s"\n//# sourceMappingURL=${bundleFile.file.name ++ ".map"}\n")) - ) { endBuffer => - JS.let( - JS.ref("Buffer") - .dot("concat") - .apply(JS.arr(concat.dot("content"), endBuffer))) { - result => - fs.dot("writeFileSync") - .apply(JS.str(bundleFile.file.absolutePath), result) - } + JS.let( + JS.ref("Buffer") + .dot("from") + .apply(JS.str(s"\n//# sourceMappingURL=${bundleFile.file.name ++ ".map"}\n"))) { endBuffer => + JS.let( + JS.ref("Buffer") + .dot("concat") + .apply(JS.arr(concat.dot("content"), endBuffer))) { result => + fs.dot("writeFileSync") + .apply(JS.str(bundleFile.file.absolutePath), result) + } }, fs.dot("writeFileSync") - .apply(JS.str(bundleFile.file.absolutePath ++ ".map"), - concat.dot("sourceMap")) + .apply(JS.str(bundleFile.file.absolutePath ++ ".map"), concat.dot("sourceMap")) ) } } diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/JSPrinters.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/JSPrinters.scala index a6575346..651d3283 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/JSPrinters.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/JSPrinters.scala @@ -38,7 +38,7 @@ private[util] object JSPrinters { def printTopLevelTree(tree: Tree): Unit = { tree match { case Skip() => - // do not print anything + // do not print anything case tree: Block => var rest = tree.stats while (rest.nonEmpty) { @@ -54,8 +54,8 @@ private[util] object JSPrinters { } protected def shouldPrintSepAfterTree(tree: Tree): Boolean = tree match { - case _:DocComment | _:FunctionDef | _:ClassDef => false - case _ => true + case _: DocComment | _: FunctionDef | _: ClassDef => false + case _ => true } protected def printRow(ts: List[Tree], start: Char, end: Char): Unit = { @@ -341,7 +341,7 @@ private[util] object JSPrinters { case DotSelect(qualifier, item) => qualifier match { - case _:IntLiteral | _:DoubleLiteral => + case _: IntLiteral | _: DoubleLiteral => print("(") print(qualifier) print(")") @@ -381,10 +381,10 @@ private[util] object JSPrinters { print("typeof ") } else { (op: @switch) match { - case + => print('+') - case - => print('-') - case ~ => print('~') - case ! => print('!') + case + => print('+') + case - => print('-') + case ~ => print('~') + case ! => print('!') case `typeof` => print("typeof ") } } diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/JSTrees.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/JSTrees.scala index e6a4fc11..6b4e716e 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/JSTrees.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/JSTrees.scala @@ -9,6 +9,7 @@ private[util] object JSTrees { /** AST node of JavaScript. */ abstract sealed class Tree { + def show: String = { val writer = new java.io.StringWriter val printer = new JSPrinters.JSTreePrinter(writer) @@ -26,17 +27,16 @@ private[util] object JSTrees { sealed trait PropertyName sealed case class Ident(name: String) extends PropertyName { - require(Ident.isValidJSIdentifierName(name), - s"'$name' is not a valid JS identifier name") + require(Ident.isValidJSIdentifierName(name), s"'$name' is not a valid JS identifier name") } object Ident { - /** Tests whether the given string is a valid `IdentifierName` for the - * ECMAScript language specification. - * - * This does not exclude keywords, as they can be used as identifiers in - * some productions, notably as property names. - */ + + /** Tests whether the given string is a valid `IdentifierName` for the ECMAScript language specification. + * + * This does not exclude keywords, as they can be used as identifiers in some productions, notably as property + * names. + */ def isValidJSIdentifierName(name: String): Boolean = { // scalastyle:off return // This method is called on every `Ident` creation; it should be fast. @@ -85,21 +85,23 @@ private[util] object JSTrees { sealed case class Skip() extends Tree sealed class Block private (val stats: List[Tree]) extends Tree { + override def toString(): String = stats.mkString("Block(", ",", ")") } object Block { + def apply(stats: List[Tree]): Tree = { val flattenedStats = stats flatMap { - case Skip() => Nil + case Skip() => Nil case Block(subStats) => subStats - case other => other :: Nil + case other => other :: Nil } flattenedStats match { - case Nil => Skip() + case Nil => Skip() case only :: Nil => only - case _ => new Block(flattenedStats) + case _ => new Block(flattenedStats) } } @@ -112,10 +114,12 @@ private[util] object JSTrees { sealed case class Labeled(label: Ident, body: Tree) extends Tree sealed case class Assign(lhs: Tree, rhs: Tree) extends Tree { - require(lhs match { - case _:VarRef | _:DotSelect | _:BracketSelect => true - case _ => false - }, s"Invalid lhs for Assign: $lhs") + require( + lhs match { + case _: VarRef | _: DotSelect | _: BracketSelect => true + case _ => false + }, + s"Invalid lhs for Assign: $lhs") } sealed case class Return(expr: Tree) extends Tree @@ -152,39 +156,39 @@ private[util] object JSTrees { sealed case class BracketSelect(qualifier: Tree, item: Tree) extends Tree - /** Syntactic apply. - * It is a method call if fun is a dot-select or bracket-select. It is a - * function call otherwise. - */ + /** Syntactic apply. It is a method call if fun is a dot-select or bracket-select. It is a function call otherwise. + */ sealed case class Apply(fun: Tree, args: List[Tree]) extends Tree /** Dynamic `import(arg)`. */ sealed case class ImportCall(arg: Tree) extends Tree /** `...items`, the "spread" operator of ECMAScript 6. - * - * It is only valid in ECMAScript 6, in the `args`/`items` of a [[New]], - * [[Apply]], or [[ArrayConstr]]. - * - * @param items An iterable whose items will be spread - */ + * + * It is only valid in ECMAScript 6, in the `args`/`items` of a [[New]], [[Apply]], or [[ArrayConstr]]. + * + * @param items + * An iterable whose items will be spread + */ sealed case class Spread(items: Tree) extends Tree sealed case class Delete(prop: Tree) extends Tree { - require(prop match { - case _:DotSelect | _:BracketSelect => true - case _ => false - }, s"Invalid prop for Delete: $prop") + require( + prop match { + case _: DotSelect | _: BracketSelect => true + case _ => false + }, + s"Invalid prop for Delete: $prop") } /** Unary operation (always preserves pureness). - * - * Operations which do not preserve pureness are not allowed in this tree. - * These are notably ++ and -- - */ + * + * Operations which do not preserve pureness are not allowed in this tree. These are notably ++ and -- + */ sealed case class UnaryOp(op: UnaryOp.Code, lhs: Tree) extends Tree object UnaryOp { + /** Codes are raw Ints to be able to switch-match on them. */ type Code = Int @@ -200,13 +204,13 @@ private[util] object JSTrees { sealed case class IncDec(prefix: Boolean, inc: Boolean, arg: Tree) extends Tree /** Binary operation (always preserves pureness). - * - * Operations which do not preserve pureness are not allowed in this tree. - * These are notably +=, -=, *=, /= and %= - */ + * + * Operations which do not preserve pureness are not allowed in this tree. These are notably +=, -=, *=, /= and %= + */ sealed case class BinaryOp(op: BinaryOp.Code, lhs: Tree, rhs: Tree) extends Tree object BinaryOp { + /** Codes are raw Ints to be able to switch-match on them. */ type Code = Int @@ -288,33 +292,28 @@ private[util] object JSTrees { // ECMAScript 6 modules /** The name of an ES module export. - * - * It must be a valid `IdentifierName`, as tested by - * [[ExportName.isValidExportName]]. - */ + * + * It must be a valid `IdentifierName`, as tested by [[ExportName.isValidExportName]]. + */ sealed case class ExportName(name: String) { require(ExportName.isValidExportName(name), s"'$name' is not a valid export name") } object ExportName { + /** Tests whether a string is a valid export name. - * - * A string is a valid export name if and only if it is a valid ECMAScript - * `IdentifierName`, which is defined in - * [[http://www.ecma-international.org/ecma-262/6.0/#sec-names-and-keywords - * Section 11.6 of the ECMAScript 2015 specification]]. - * - * Currently, this implementation is buggy in some corner cases, as it does - * not accept code points with the Unicode properties `Other_ID_Start` and - * `Other_ID_Continue`. For example, - * `isValidIdentifierName(0x2118.toChar.toString)` will return `false` - * instead of `true`. - * - * In theory, it does not really account for code points with the Unicode - * properties `Pattern_Syntax` and `Pattern_White_Space`, which should be - * rejected. However, with the current version of Unicode (9.0.0), there - * seems to be no such character that would be accepted by this method. - */ + * + * A string is a valid export name if and only if it is a valid ECMAScript `IdentifierName`, which is defined in + * [[http://www.ecma-international.org/ecma-262/6.0/#sec-names-and-keywords Section 11.6 of the ECMAScript 2015 specification]]. + * + * Currently, this implementation is buggy in some corner cases, as it does not accept code points with the Unicode + * properties `Other_ID_Start` and `Other_ID_Continue`. For example, + * `isValidIdentifierName(0x2118.toChar.toString)` will return `false` instead of `true`. + * + * In theory, it does not really account for code points with the Unicode properties `Pattern_Syntax` and + * `Pattern_White_Space`, which should be rejected. However, with the current version of Unicode (9.0.0), there + * seems to be no such character that would be accepted by this method. + */ final def isValidExportName(name: String): Boolean = { // scalastyle:off return import java.lang.Character._ @@ -323,8 +322,8 @@ private[util] object JSTrees { isUnicodeIdentifierStart(cp) || cp == '$' || cp == '_' def isJSIdentifierPart(cp: Int): Boolean = { - val ZWNJ = 0x200c - val ZWJ = 0x200d + val ZWNJ = 0x200C + val ZWJ = 0x200D isUnicodeIdentifierPart(cp) || cp == '$' || cp == '_' || cp == ZWNJ || cp == ZWJ } @@ -349,40 +348,37 @@ private[util] object JSTrees { } /** `import` statement, except namespace import. - * - * This corresponds to the following syntax: - * {{{ - * import { as , ..., as } from - * }}} - * The `_1` parts of bindings are therefore the identifier names that are - * imported, as specified in `export` clauses of the module. The `_2` parts - * are the names under which they are imported in the current module. - * - * Special cases: - * - When `_1.name == _2.name`, there is shorter syntax in ES, i.e., - * `import { binding } from 'from'`. - * - When `_1.name == "default"`, it is equivalent to a default import. - */ + * + * This corresponds to the following syntax: + * {{{ + * import { as , ..., as } from + * }}} + * The `_1` parts of bindings are therefore the identifier names that are imported, as specified in `export` clauses + * of the module. The `_2` parts are the names under which they are imported in the current module. + * + * Special cases: + * - When `_1.name == _2.name`, there is shorter syntax in ES, i.e., `import { binding } from 'from'`. + * - When `_1.name == "default"`, it is equivalent to a default import. + */ sealed case class Import(bindings: List[(ExportName, Ident)], from: StringLiteral) extends Tree /** Namespace `import` statement. - * - * This corresponds to the following syntax: - * {{{ - * import * as from - * }}} - */ + * + * This corresponds to the following syntax: + * {{{ + * import * as from + * }}} + */ sealed case class ImportNamespace(binding: Ident, from: StringLiteral) extends Tree /** `export` statement. - * - * This corresponds to the following syntax: - * {{{ - * export { as , ..., as } - * }}} - * The `_1` parts of bindings are therefore the identifiers from the current - * module that are exported. The `_2` parts are the names under which they - * are exported to other modules. - */ + * + * This corresponds to the following syntax: + * {{{ + * export { as , ..., as } + * }}} + * The `_1` parts of bindings are therefore the identifiers from the current module that are exported. The `_2` parts + * are the names under which they are exported to other modules. + */ sealed case class Export(bindings: List[(Ident, ExportName)]) extends Tree } diff --git a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/ScalaJSNativeLibraries.scala b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/ScalaJSNativeLibraries.scala index f0494416..15290068 100644 --- a/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/ScalaJSNativeLibraries.scala +++ b/sbt-scalajs-bundler/src/main/scala/scalajsbundler/util/ScalaJSNativeLibraries.scala @@ -13,24 +13,31 @@ private[scalajsbundler] object ScalaJSNativeLibraries { // Copied from https://github.com/scala-js/jsdependencies def apply(fullClasspath: Seq[Attributed[File]]): Seq[(String, Path)] = { - collectFromClasspath(fullClasspath, - "*.js", collectJar = jsFilesInJar, + collectFromClasspath( + fullClasspath, + "*.js", + collectJar = jsFilesInJar, collectFile = (f, relPath) => relPath -> f.toPath()) } /** Collect certain file types from a classpath. * - * @param cp Classpath to collect from - * @param filter Filter for (real) files of interest (not in jars) - * @param collectJar Collect elements from a jar (called for all jars) - * @param collectFile Collect a single file. Params are the file and the - * relative path of the file (to its classpath entry root). - * @return Collected elements attributed with physical files they originated - * from (key: scalaJSSourceFiles). + * @param cp + * Classpath to collect from + * @param filter + * Filter for (real) files of interest (not in jars) + * @param collectJar + * Collect elements from a jar (called for all jars) + * @param collectFile + * Collect a single file. Params are the file and the relative path of the file (to its classpath entry root). + * @return + * Collected elements attributed with physical files they originated from (key: scalaJSSourceFiles). */ - private def collectFromClasspath[T](cp: Def.Classpath, filter: FileFilter, - collectJar: File => Seq[T], - collectFile: (File, String) => T): Seq[T] = { + private def collectFromClasspath[T]( + cp: Def.Classpath, + filter: FileFilter, + collectJar: File => Seq[T], + collectFile: (File, String) => T): Seq[T] = { val results = Seq.newBuilder[T] @@ -45,8 +52,7 @@ private[scalajsbundler] object ScalaJSNativeLibraries { results += collectFile(file, relPath) } } else { - throw new IllegalArgumentException( - "Illegal classpath entry: " + cpEntry.getPath) + throw new IllegalArgumentException("Illegal classpath entry: " + cpEntry.getPath) } } @@ -56,8 +62,7 @@ private[scalajsbundler] object ScalaJSNativeLibraries { private def jsFilesInJar(jar: File): List[(String, Path)] = jarListEntries(jar, _.endsWith(".js")) - private def jarListEntries[T](jar: File, - p: String => Boolean): List[(String, Path)] = { + private def jarListEntries[T](jar: File, p: String => Boolean): List[(String, Path)] = { import java.util.zip._ @@ -97,7 +102,8 @@ private[scalajsbundler] object ScalaJSNativeLibraries { } } - Iterator.continually(stream.getNextEntry()) + Iterator + .continually(stream.getNextEntry()) .takeWhile(_ != null) .filter(e => p(e.getName)) .map(makeVF) diff --git a/sbt-web-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/NpmAssets.scala b/sbt-web-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/NpmAssets.scala index 1e32e9ce..8c4333f3 100644 --- a/sbt-web-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/NpmAssets.scala +++ b/sbt-web-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/NpmAssets.scala @@ -7,15 +7,17 @@ import scalajsbundler.sbtplugin.ScalaJSBundlerPlugin.autoImport.npmUpdate object NpmAssets { - /** - * {{{ + /** {{{ * npmAssets ++= NpmAssets.ofProject(client) { _ / "some-package" / "some-relevant" ** "files" }.value * }}} * - * @return A task producing a sequence of files paired with their path relative to the `node_modules` directory - * of `project`. - * @param project The project that depends on the NPM package containing the assets to publish. - * @param assets A function that finds files given the `node_modules` directory of `project` + * @return + * A task producing a sequence of files paired with their path relative to the `node_modules` directory of + * `project`. + * @param project + * The project that depends on the NPM package containing the assets to publish. + * @param assets + * A function that finds files given the `node_modules` directory of `project` */ def ofProject(project: ProjectReference)(assets: File => PathFinder): Def.Initialize[Task[Seq[PathMapping]]] = Def.task { diff --git a/sbt-web-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/WebScalaJSBundlerPlugin.scala b/sbt-web-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/WebScalaJSBundlerPlugin.scala index e47b0f0e..69f116e3 100644 --- a/sbt-web-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/WebScalaJSBundlerPlugin.scala +++ b/sbt-web-scalajs-bundler/src/main/scala/scalajsbundler/sbtplugin/WebScalaJSBundlerPlugin.scala @@ -12,28 +12,25 @@ import webscalajs.WebScalaJS.autoImport._ import scala.collection.immutable.Nil import scalajsbundler.sbtplugin.ScalaJSBundlerPlugin.autoImport._ -/** - * If WebScalaJS is enabled, tweaks the pipelineStage so that the bundle is produced - * as an sbt-web asset. +/** If WebScalaJS is enabled, tweaks the pipelineStage so that the bundle is produced as an sbt-web asset. * - * = Tasks and Settings = + * =Tasks and Settings= * * See the [[WebScalaJSBundlerPlugin.autoImport autoImport]] member. */ object WebScalaJSBundlerPlugin extends AutoPlugin { - /** - * @groupname settings Settings - */ + /** @groupname settings Settings */ object autoImport { - /** - * Sequence of PathMapping’s to include to sbt-web’s assets. + /** Sequence of PathMapping’s to include to sbt-web’s assets. * - * @see [[scalajsbundler.sbtplugin.NpmAssets.ofProject NpmAssets.ofProject]] + * @see + * [[scalajsbundler.sbtplugin.NpmAssets.ofProject NpmAssets.ofProject]] * @group settings */ - val npmAssets: TaskKey[Seq[PathMapping]] = taskKey[Seq[PathMapping]]("Assets (resources that are not CommonJS modules) imported from the NPM packages") + val npmAssets: TaskKey[Seq[PathMapping]] = + taskKey[Seq[PathMapping]]("Assets (resources that are not CommonJS modules) imported from the NPM packages") val NpmAssets = scalajsbundler.sbtplugin.NpmAssets @@ -51,13 +48,14 @@ object WebScalaJSBundlerPlugin extends AutoPlugin { val allFrontendProjectResourceDirectories: Def.Initialize[Seq[File]] = Def.settingDyn { val projectRefs = scalaJSProjects.value.map(Project.projectToRef) - projectRefs.map { project => - Def.setting { - (resourceDirectories in Compile in project).value + projectRefs + .map { project => + Def.setting { + (resourceDirectories in Compile in project).value + } } - }.foldLeft(Def.setting(Seq.empty[File]))((acc, resourceDirectories) => - Def.setting(acc.value ++ resourceDirectories.value) - ) + .foldLeft(Def.setting(Seq.empty[File]))((acc, resourceDirectories) => + Def.setting(acc.value ++ resourceDirectories.value)) } val bundlesWithSourceMaps: Def.Initialize[Task[Seq[(File, String)]]] = @@ -81,7 +79,8 @@ object WebScalaJSBundlerPlugin extends AutoPlugin { } } } - .foldLeft(Def.task(Seq.empty[((File, String), Boolean)]))((acc, bundleFiles) => Def.task(acc.value ++ bundleFiles.value)) + .foldLeft(Def.task(Seq.empty[((File, String), Boolean)]))((acc, bundleFiles) => + Def.task(acc.value ++ bundleFiles.value)) .value bundles.flatMap { case ((file, path), bundleSourceMapsEnabled) =>