From 66b5f3623220b7411395835efef1bff406aa1b94 Mon Sep 17 00:00:00 2001 From: Domantas Petrauskas Date: Sun, 22 Oct 2023 12:06:30 +0300 Subject: [PATCH 1/9] Add setting for esbuild config for Scala.js modules --- .../EsbuildScalaJSModuleConfiguration.scala | 13 +++++++++++++ .../scalajsesbuild/ScalaJSEsbuildPlugin.scala | 15 +++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScalaJSModuleConfiguration.scala diff --git a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScalaJSModuleConfiguration.scala b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScalaJSModuleConfiguration.scala new file mode 100644 index 00000000..f7fb9c44 --- /dev/null +++ b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScalaJSModuleConfiguration.scala @@ -0,0 +1,13 @@ +package scalajsesbuild + +import scalajsesbuild.EsbuildScalaJSModuleConfiguration.EsbuildPlatform + +final class EsbuildScalaJSModuleConfiguration(val platform: EsbuildPlatform) + +object EsbuildScalaJSModuleConfiguration { + sealed trait EsbuildPlatform + object EsbuildPlatform { + case object Browser extends EsbuildPlatform + case object Node extends EsbuildPlatform + } +} diff --git a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/ScalaJSEsbuildPlugin.scala b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/ScalaJSEsbuildPlugin.scala index 7fcffcd5..c5632db5 100644 --- a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/ScalaJSEsbuildPlugin.scala +++ b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/ScalaJSEsbuildPlugin.scala @@ -11,6 +11,7 @@ import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport.fullLinkJS import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport.jsEnvInput import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport.scalaJSLinkerConfig import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport.scalaJSLinkerOutputDirectory +import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport.scalaJSModuleInitializers import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport.scalaJSStage import org.scalajs.sbtplugin.Stage import sbt.* @@ -41,6 +42,10 @@ object ScalaJSEsbuildPlugin extends AutoPlugin { taskKey( "Compiles module and copies output to target directory" ) + val esbuildScalaJSModuleConfigurations + : TaskKey[Map[String, EsbuildScalaJSModuleConfiguration]] = taskKey( + "esbuild configurations for Scala.js modules" + ) val esbuildBundleScript: TaskKey[String] = taskKey( "esbuild script used for bundling" ) // TODO consider doing the writing of the script upon call of this task, then use FileChanges to track changes to the script @@ -76,6 +81,16 @@ object ScalaJSEsbuildPlugin extends AutoPlugin { target.value / "streams" / "test" / "_global" / "esbuildCopyResources" ) } + }, + esbuildScalaJSModuleConfigurations := { + val modules = (Compile / scalaJSModuleInitializers).value + modules + .map(module => + module.moduleID -> new EsbuildScalaJSModuleConfiguration( + EsbuildScalaJSModuleConfiguration.EsbuildPlatform.Browser + ) + ) + .toMap } ) ++ inConfig(Compile)(perConfigSettings) ++ From c34a82d9f5d3eb6182adce3bc123c0aa342bbcc0 Mon Sep 17 00:00:00 2001 From: Domantas Petrauskas Date: Sun, 22 Oct 2023 12:25:20 +0300 Subject: [PATCH 2/9] Add impl notes --- .../src/main/scala/scalajsesbuild/ScalaJSEsbuildPlugin.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/ScalaJSEsbuildPlugin.scala b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/ScalaJSEsbuildPlugin.scala index c5632db5..e8601749 100644 --- a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/ScalaJSEsbuildPlugin.scala +++ b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/ScalaJSEsbuildPlugin.scala @@ -83,11 +83,12 @@ object ScalaJSEsbuildPlugin extends AutoPlugin { } }, esbuildScalaJSModuleConfigurations := { - val modules = (Compile / scalaJSModuleInitializers).value + val modules = + (Compile / scalaJSModuleInitializers).value // TODO must be scoped per config - cannot exclude main initializers modules .map(module => module.moduleID -> new EsbuildScalaJSModuleConfiguration( - EsbuildScalaJSModuleConfiguration.EsbuildPlatform.Browser + EsbuildScalaJSModuleConfiguration.EsbuildPlatform.Browser // TODO set based on scalaJSLinkerConfig.value.moduleKind ) ) .toMap From 97406e3780e65db8c3d2e416e4be52230779d58c Mon Sep 17 00:00:00 2001 From: Domantas Petrauskas Date: Wed, 1 Nov 2023 16:39:15 +0200 Subject: [PATCH 3/9] Add neutral platform --- .../scala/scalajsesbuild/EsbuildScalaJSModuleConfiguration.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScalaJSModuleConfiguration.scala b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScalaJSModuleConfiguration.scala index f7fb9c44..9932b937 100644 --- a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScalaJSModuleConfiguration.scala +++ b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScalaJSModuleConfiguration.scala @@ -9,5 +9,6 @@ object EsbuildScalaJSModuleConfiguration { object EsbuildPlatform { case object Browser extends EsbuildPlatform case object Node extends EsbuildPlatform + case object Neutral extends EsbuildPlatform } } From dd8660efaf2cf8dccc21f5200bfb65ed01a424f5 Mon Sep 17 00:00:00 2001 From: Domantas Petrauskas Date: Thu, 2 Nov 2023 14:31:28 +0200 Subject: [PATCH 4/9] Add per platform bundling in esbuild plugin --- .../EsbuildScalaJSModuleConfiguration.scala | 6 ++- .../scala/scalajsesbuild/EsbuildScripts.scala | 39 ++++++++++------- .../scalajsesbuild/ScalaJSEsbuildPlugin.scala | 42 +++++++++++-------- .../main/scala/scalajsesbuild/package.scala | 30 +++++++++++++ .../basic-node-project/build.sbt | 4 ++ 5 files changed, 87 insertions(+), 34 deletions(-) diff --git a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScalaJSModuleConfiguration.scala b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScalaJSModuleConfiguration.scala index 9932b937..0bcb427a 100644 --- a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScalaJSModuleConfiguration.scala +++ b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScalaJSModuleConfiguration.scala @@ -2,7 +2,11 @@ package scalajsesbuild import scalajsesbuild.EsbuildScalaJSModuleConfiguration.EsbuildPlatform -final class EsbuildScalaJSModuleConfiguration(val platform: EsbuildPlatform) +final class EsbuildScalaJSModuleConfiguration(val platform: EsbuildPlatform) { + override def toString: String = { + s"EsbuildScalaJSModuleConfiguration($platform)" + } +} object EsbuildScalaJSModuleConfiguration { sealed trait EsbuildPlatform diff --git a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScripts.scala b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScripts.scala index f99fd173..d15a24be 100644 --- a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScripts.scala +++ b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScripts.scala @@ -5,6 +5,7 @@ object EsbuildScripts { private[scalajsesbuild] def esbuildOptions = { // language=JS """const esbuildOptions = ( + | platform, | entryPoints, | outDirectory, | outputFilesDirectory, @@ -64,6 +65,7 @@ object EsbuildScripts { | } | | return { + | platform: platform, | entryPoints: entryPoints, | bundle: true, | outdir: path.normalize(outDirectory), @@ -93,29 +95,34 @@ object EsbuildScripts { private[scalajsesbuild] def bundle = { // language=JS """const bundle = async ( - | entryPoints, + | entryPointsByPlatform, | outDirectory, | outputFilesDirectory, | hashOutputFiles, - | minify, - | metaFileName + | minify |) => { | const esbuild = require('esbuild'); - | const fs = require('fs'); | - | const result = await esbuild.build( - | esbuildOptions( - | entryPoints, - | outDirectory, - | outputFilesDirectory, - | hashOutputFiles, - | minify - | ) + | return await Promise.all( + | Object.keys(entryPointsByPlatform).reduce((acc, platform) => { + | const platformMetafilePromise = esbuild + | .build( + | esbuildOptions( + | platform, + | entryPointsByPlatform[platform], + | outDirectory, + | outputFilesDirectory, + | hashOutputFiles, + | minify + | ) + | ) + | .then((result) => { + | return {[platform]: result.metafile}; + | }); + | acc.push(platformMetafilePromise); + | return acc; + | }, []) | ); - | - | fs.writeFileSync(metaFileName, JSON.stringify(result.metafile)); - | - | return result.metafile; |}; |""".stripMargin } diff --git a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/ScalaJSEsbuildPlugin.scala b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/ScalaJSEsbuildPlugin.scala index e8601749..629f0c4d 100644 --- a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/ScalaJSEsbuildPlugin.scala +++ b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/ScalaJSEsbuildPlugin.scala @@ -63,9 +63,6 @@ object ScalaJSEsbuildPlugin extends AutoPlugin { import autoImport.* override lazy val projectSettings: Seq[Setting[?]] = Seq( - scalaJSLinkerConfig ~= { - _.withModuleKind(ModuleKind.ESModule) - }, esbuildRunner := EsbuildRunner.Default, esbuildResourcesDirectory := baseDirectory.value / "esbuild", esbuildPackageManager := PackageManager.Npm, @@ -81,23 +78,27 @@ object ScalaJSEsbuildPlugin extends AutoPlugin { target.value / "streams" / "test" / "_global" / "esbuildCopyResources" ) } - }, - esbuildScalaJSModuleConfigurations := { - val modules = - (Compile / scalaJSModuleInitializers).value // TODO must be scoped per config - cannot exclude main initializers - modules - .map(module => - module.moduleID -> new EsbuildScalaJSModuleConfiguration( - EsbuildScalaJSModuleConfiguration.EsbuildPlatform.Browser // TODO set based on scalaJSLinkerConfig.value.moduleKind - ) - ) - .toMap } ) ++ inConfig(Compile)(perConfigSettings) ++ inConfig(Test)(perConfigSettings) private lazy val perConfigSettings: Seq[Setting[?]] = Seq( + esbuildScalaJSModuleConfigurations := { + val moduleKind = scalaJSLinkerConfig.value.moduleKind + val esbuildModuleConfiguration = new EsbuildScalaJSModuleConfiguration( + platform = moduleKind match { + case ModuleKind.CommonJSModule => + EsbuildScalaJSModuleConfiguration.EsbuildPlatform.Node + case _ => + EsbuildScalaJSModuleConfiguration.EsbuildPlatform.Browser + } + ) + val modules = scalaJSModuleInitializers.value + modules + .map(module => module.moduleID -> esbuildModuleConfiguration) + .toMap + }, esbuildInstall / crossTarget := { crossTarget.value / "esbuild" / @@ -209,7 +210,13 @@ object ScalaJSEsbuildPlugin extends AutoPlugin { stageTask / esbuildBundle / crossTarget := (esbuildInstall / crossTarget).value / "out", stageTask / esbuildBundleScript := { val stageTaskReport = stageTask.value.data - val entryPoints = jsFileNames(stageTaskReport).toSeq + val moduleConfigurations = esbuildScalaJSModuleConfigurations.value + val entryPointsByPlatform = + extractEntryPointsByPlatform(stageTaskReport, moduleConfigurations) + val entryPointsByPlatformJs = "{" + entryPointsByPlatform + .foldLeft("") { case (acc, (platform, entryPoints)) => + acc + s"${platform.toString.toLowerCase}:${entryPoints.map("'" + _ + "'").mkString("[", ",", "]")}" + } + "}" val targetDirectory = (esbuildInstall / crossTarget).value val outputDirectory = (stageTask / esbuildBundle / crossTarget).value @@ -221,6 +228,7 @@ object ScalaJSEsbuildPlugin extends AutoPlugin { s"Target directory [$targetDirectory] must be parent directory of output directory [$outputDirectory]" ) ) + val relativeOutputDirectoryJs = s"'$relativeOutputDirectory'" val minify = if (configuration.value == Test) { false @@ -235,8 +243,8 @@ object ScalaJSEsbuildPlugin extends AutoPlugin { |${EsbuildScripts.bundle} | |bundle( - | ${entryPoints.map("'" + _ + "'").mkString("[", ",", "]")}, - | ${s"'$relativeOutputDirectory'"}, + | $entryPointsByPlatformJs, + | $relativeOutputDirectoryJs, | null, | false, | $minify, diff --git a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/package.scala b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/package.scala index b5f81689..8c9a8d04 100644 --- a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/package.scala +++ b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/package.scala @@ -26,6 +26,36 @@ package object scalajsesbuild { } } + private[scalajsesbuild] def extractEntryPointsByPlatform( + report: Report, + moduleConfigurations: Map[String, EsbuildScalaJSModuleConfiguration] + ) = { + report match { + case report: unstable.ReportImpl => + report.publicModules + .foldLeft( + Map.empty[EsbuildScalaJSModuleConfiguration.EsbuildPlatform, Set[ + String + ]] + ) { case (acc, publicModule) => + val platform = moduleConfigurations + .getOrElse( + publicModule.moduleID, + sys.error( + s"esbuild module configuration missing for moduleID [${publicModule.moduleID}]" + ) + ) + .platform + acc.updated( + platform, + acc.getOrElse(platform, Set.empty) + publicModule.jsFileName + ) + } + case unhandled => + sys.error(s"Unhandled report type [$unhandled]") + } + } + private[scalajsesbuild] def jsFileNames(report: Report) = { report match { case report: unstable.ReportImpl => diff --git a/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/build.sbt b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/build.sbt index bf36adba..3ad402cd 100644 --- a/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/build.sbt +++ b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/build.sbt @@ -2,6 +2,10 @@ enablePlugins(ScalaJSEsbuildPlugin) scalaVersion := "2.13.8" +scalaJSLinkerConfig ~= { + _.withModuleKind(ModuleKind.CommonJSModule) +} + scalaJSUseMainModuleInitializer := true libraryDependencies += "org.scalatest" %%% "scalatest" % "3.2.15" % "test" From dbe669092e2e08c95eb74e0ee045927fd2c75654 Mon Sep 17 00:00:00 2001 From: Domantas Petrauskas Date: Fri, 3 Nov 2023 10:16:09 +0200 Subject: [PATCH 5/9] Add separate script for per platform bundling in esbuild plugin --- .../scala/scalajsesbuild/EsbuildScripts.scala | 54 ++++++++++++++----- .../scalajsesbuild/ScalaJSEsbuildPlugin.scala | 7 +-- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScripts.scala b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScripts.scala index d15a24be..a45e6421 100644 --- a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScripts.scala +++ b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScripts.scala @@ -95,7 +95,8 @@ object EsbuildScripts { private[scalajsesbuild] def bundle = { // language=JS """const bundle = async ( - | entryPointsByPlatform, + | platform, + | entryPoints, | outDirectory, | outputFilesDirectory, | hashOutputFiles, @@ -103,11 +104,36 @@ object EsbuildScripts { |) => { | const esbuild = require('esbuild'); | - | return await Promise.all( - | Object.keys(entryPointsByPlatform).reduce((acc, platform) => { - | const platformMetafilePromise = esbuild - | .build( - | esbuildOptions( + | const result = await esbuild.build( + | esbuildOptions( + | platform, + | entryPoints, + | outDirectory, + | outputFilesDirectory, + | hashOutputFiles, + | minify + | ) + | ); + | + | return result.metafile; + |}; + |""".stripMargin + } + + private[scalajsesbuild] def bundleByPlatform = { + // language=JS + """const bundleByPlatform = async ( + | entryPointsByPlatform, + | outDirectory, + | outputFilesDirectory, + | hashOutputFiles, + | minify + |) => { + | return await Promise + | .all( + | Object.keys(entryPointsByPlatform).reduce((acc, platform) => { + | const platformMetafilePromise = + | bundle( | platform, | entryPointsByPlatform[platform], | outDirectory, @@ -115,14 +141,14 @@ object EsbuildScripts { | hashOutputFiles, | minify | ) - | ) - | .then((result) => { - | return {[platform]: result.metafile}; - | }); - | acc.push(platformMetafilePromise); - | return acc; - | }, []) - | ); + | .then((metafile) => { + | return {[platform]: metafile}; + | }); + | acc.push(platformMetafilePromise); + | return acc; + | }, []) + | ) + | .then((results) => results.reduce((acc, result) => ({...acc, ...result}) , {})); |}; |""".stripMargin } diff --git a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/ScalaJSEsbuildPlugin.scala b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/ScalaJSEsbuildPlugin.scala index 629f0c4d..3df0536d 100644 --- a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/ScalaJSEsbuildPlugin.scala +++ b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/ScalaJSEsbuildPlugin.scala @@ -242,13 +242,14 @@ object ScalaJSEsbuildPlugin extends AutoPlugin { | |${EsbuildScripts.bundle} | - |bundle( + |${EsbuildScripts.bundleByPlatform} + | + |bundleByPlatform( | $entryPointsByPlatformJs, | $relativeOutputDirectoryJs, | null, | false, - | $minify, - | 'sbt-scalajs-esbuild-bundle-meta.json' + | $minify |); |""".stripMargin }, From 1bc0f88d4bdc65f411ff71180dccec927ee2063c Mon Sep 17 00:00:00 2001 From: Domantas Petrauskas Date: Fri, 3 Nov 2023 10:33:51 +0200 Subject: [PATCH 6/9] Only support browser platform bundling in web plugin --- .../scalajsesbuild/EsbuildWebScripts.scala | 1 + .../ScalaJSEsbuildWebPlugin.scala | 33 +++++++++++++++---- .../EsbuildScalaJSModuleConfiguration.scala | 4 ++- .../scala/scalajsesbuild/EsbuildScripts.scala | 8 ++++- .../scalajsesbuild/ScalaJSEsbuildPlugin.scala | 2 +- 5 files changed, 39 insertions(+), 9 deletions(-) diff --git a/sbt-scalajs-esbuild-web/src/main/scala/scalajsesbuild/EsbuildWebScripts.scala b/sbt-scalajs-esbuild-web/src/main/scala/scalajsesbuild/EsbuildWebScripts.scala index 73d18413..fbaba551 100644 --- a/sbt-scalajs-esbuild-web/src/main/scala/scalajsesbuild/EsbuildWebScripts.scala +++ b/sbt-scalajs-esbuild-web/src/main/scala/scalajsesbuild/EsbuildWebScripts.scala @@ -139,6 +139,7 @@ object EsbuildWebScripts { | | const ctx = await esbuild.context({ | ...esbuildOptions( + | 'browser', | entryPoints, | outDirectory, | outputFilesDirectory, diff --git a/sbt-scalajs-esbuild-web/src/main/scala/scalajsesbuild/ScalaJSEsbuildWebPlugin.scala b/sbt-scalajs-esbuild-web/src/main/scala/scalajsesbuild/ScalaJSEsbuildWebPlugin.scala index 5ddbef16..c3bbbabf 100644 --- a/sbt-scalajs-esbuild-web/src/main/scala/scalajsesbuild/ScalaJSEsbuildWebPlugin.scala +++ b/sbt-scalajs-esbuild-web/src/main/scala/scalajsesbuild/ScalaJSEsbuildWebPlugin.scala @@ -1,7 +1,9 @@ package scalajsesbuild import org.scalajs.jsenv.Input.Script +import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport.ModuleKind import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport.jsEnvInput +import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport.scalaJSLinkerConfig import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport.scalaJSStage import org.scalajs.sbtplugin.Stage import org.typelevel.jawn.ast.JObject @@ -14,8 +16,9 @@ import scalajsesbuild.ScalaJSEsbuildPlugin.autoImport.esbuildBundleScript import scalajsesbuild.ScalaJSEsbuildPlugin.autoImport.esbuildCompile import scalajsesbuild.ScalaJSEsbuildPlugin.autoImport.esbuildInstall import scalajsesbuild.ScalaJSEsbuildPlugin.autoImport.esbuildRunner - +import scalajsesbuild.ScalaJSEsbuildPlugin.autoImport.esbuildScalaJSModuleConfigurations import java.nio.file.Path + import scala.sys.process.* object ScalaJSEsbuildWebPlugin extends AutoPlugin { @@ -39,6 +42,9 @@ object ScalaJSEsbuildWebPlugin extends AutoPlugin { override lazy val projectSettings: Seq[Setting[?]] = Seq( + scalaJSLinkerConfig ~= { + _.withModuleKind(ModuleKind.ESModule) + }, esbuildBundleHtmlEntryPoints := Seq( Path.of("index.html") ) @@ -93,7 +99,13 @@ object ScalaJSEsbuildWebPlugin extends AutoPlugin { Seq( stageTask / esbuildBundleScript := { val stageTaskReport = stageTask.value.data - val entryPoints = jsFileNames(stageTaskReport).toSeq + val moduleConfigurations = esbuildScalaJSModuleConfigurations.value + val entryPoints = + extractEntryPointsByPlatform(stageTaskReport, moduleConfigurations) + .getOrElse( + EsbuildScalaJSModuleConfiguration.EsbuildPlatform.Browser, + Set.empty + ) val entryPointsJsArray = entryPoints.map("'" + _ + "'").mkString("[", ",", "]") val targetDirectory = (esbuildInstall / crossTarget).value @@ -107,6 +119,7 @@ object ScalaJSEsbuildWebPlugin extends AutoPlugin { s"Target directory [$targetDirectory] must be parent directory of output directory [$outputDirectory]" ) ) + val relativeOutputDirectoryJs = s"'$relativeOutputDirectory'" val htmlEntryPoints = esbuildBundleHtmlEntryPoints.value require( !htmlEntryPoints.forall(_.isAbsolute), @@ -132,8 +145,9 @@ object ScalaJSEsbuildWebPlugin extends AutoPlugin { |${EsbuildWebScripts.transformHtmlEntryPoints} | |const metaFilePromise = bundle( + | ${EsbuildScalaJSModuleConfiguration.EsbuildPlatform.Node.jsValue}, | $entryPointsJsArray, - | ${s"'$relativeOutputDirectory'"}, + | $relativeOutputDirectoryJs, | 'assets', | $hashOutputFiles, | $minify, @@ -144,7 +158,7 @@ object ScalaJSEsbuildWebPlugin extends AutoPlugin { | .then((metaFile) => { | transformHtmlEntryPoints( | $htmlEntryPointsJsArray, - | ${s"'$relativeOutputDirectory'"}, + | $relativeOutputDirectoryJs, | metaFile | ); | }); @@ -164,7 +178,13 @@ object ScalaJSEsbuildWebPlugin extends AutoPlugin { Seq( stageTask / esbuildServeScript := { val stageTaskReport = stageTask.value.data - val entryPoints = jsFileNames(stageTaskReport).toSeq + val moduleConfigurations = esbuildScalaJSModuleConfigurations.value + val entryPoints = + extractEntryPointsByPlatform(stageTaskReport, moduleConfigurations) + .getOrElse( + EsbuildScalaJSModuleConfiguration.EsbuildPlatform.Browser, + Set.empty + ) val entryPointsJsArray = entryPoints.map("'" + _ + "'").mkString("[", ",", "]") val targetDirectory = (esbuildInstall / crossTarget).value @@ -178,6 +198,7 @@ object ScalaJSEsbuildWebPlugin extends AutoPlugin { s"Target directory [$targetDirectory] must be parent directory of output directory [$outputDirectory]" ) ) + val relativeOutputDirectoryJs = s"'$relativeOutputDirectory'" val htmlEntryPoints = esbuildBundleHtmlEntryPoints.value require( !htmlEntryPoints.forall(_.isAbsolute), @@ -200,7 +221,7 @@ object ScalaJSEsbuildWebPlugin extends AutoPlugin { | |serve( | $entryPointsJsArray, - | ${s"'$relativeOutputDirectory'"}, + | $relativeOutputDirectoryJs, | 'assets', | 'sbt-scalajs-esbuild-serve-meta.json', | 8001, diff --git a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScalaJSModuleConfiguration.scala b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScalaJSModuleConfiguration.scala index 0bcb427a..3bdeec7f 100644 --- a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScalaJSModuleConfiguration.scala +++ b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScalaJSModuleConfiguration.scala @@ -9,7 +9,9 @@ final class EsbuildScalaJSModuleConfiguration(val platform: EsbuildPlatform) { } object EsbuildScalaJSModuleConfiguration { - sealed trait EsbuildPlatform + sealed trait EsbuildPlatform { + def jsValue: String = s"'${toString.toLowerCase}'" + } object EsbuildPlatform { case object Browser extends EsbuildPlatform case object Node extends EsbuildPlatform diff --git a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScripts.scala b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScripts.scala index a45e6421..45170eec 100644 --- a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScripts.scala +++ b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/EsbuildScripts.scala @@ -100,9 +100,11 @@ object EsbuildScripts { | outDirectory, | outputFilesDirectory, | hashOutputFiles, - | minify + | minify, + | metaFileName |) => { | const esbuild = require('esbuild'); + | const fs = require('fs'); | | const result = await esbuild.build( | esbuildOptions( @@ -115,6 +117,10 @@ object EsbuildScripts { | ) | ); | + | if (metaFileName) { + | fs.writeFileSync(metaFileName, JSON.stringify(result.metafile)); + | } + | | return result.metafile; |}; |""".stripMargin diff --git a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/ScalaJSEsbuildPlugin.scala b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/ScalaJSEsbuildPlugin.scala index 3df0536d..da9e8b5b 100644 --- a/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/ScalaJSEsbuildPlugin.scala +++ b/sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/ScalaJSEsbuildPlugin.scala @@ -215,7 +215,7 @@ object ScalaJSEsbuildPlugin extends AutoPlugin { extractEntryPointsByPlatform(stageTaskReport, moduleConfigurations) val entryPointsByPlatformJs = "{" + entryPointsByPlatform .foldLeft("") { case (acc, (platform, entryPoints)) => - acc + s"${platform.toString.toLowerCase}:${entryPoints.map("'" + _ + "'").mkString("[", ",", "]")}" + acc + s"${platform.jsValue}:${entryPoints.map("'" + _ + "'").mkString("[", ",", "]")}" } + "}" val targetDirectory = (esbuildInstall / crossTarget).value val outputDirectory = From d44726c59bd3aa88e238132c02b17e4bb3784625 Mon Sep 17 00:00:00 2001 From: Domantas Petrauskas Date: Fri, 3 Nov 2023 10:56:15 +0200 Subject: [PATCH 7/9] Fix platform in web plugin bundling task --- .../src/main/scala/scalajsesbuild/ScalaJSEsbuildWebPlugin.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sbt-scalajs-esbuild-web/src/main/scala/scalajsesbuild/ScalaJSEsbuildWebPlugin.scala b/sbt-scalajs-esbuild-web/src/main/scala/scalajsesbuild/ScalaJSEsbuildWebPlugin.scala index c3bbbabf..10de79d3 100644 --- a/sbt-scalajs-esbuild-web/src/main/scala/scalajsesbuild/ScalaJSEsbuildWebPlugin.scala +++ b/sbt-scalajs-esbuild-web/src/main/scala/scalajsesbuild/ScalaJSEsbuildWebPlugin.scala @@ -145,7 +145,7 @@ object ScalaJSEsbuildWebPlugin extends AutoPlugin { |${EsbuildWebScripts.transformHtmlEntryPoints} | |const metaFilePromise = bundle( - | ${EsbuildScalaJSModuleConfiguration.EsbuildPlatform.Node.jsValue}, + | ${EsbuildScalaJSModuleConfiguration.EsbuildPlatform.Browser.jsValue}, | $entryPointsJsArray, | $relativeOutputDirectoryJs, | 'assets', From be5ed5a200f16b519313d72b3f62396fd5c1c511 Mon Sep 17 00:00:00 2001 From: Domantas Petrauskas Date: Fri, 3 Nov 2023 17:14:58 +0200 Subject: [PATCH 8/9] Implement proper node project test --- .../basic-node-project/esbuild/package.json | 6 ++---- .../src/main/scala/example/Main.scala | 11 ++++++++--- .../src/main/scala/example/facade/Fs.scala | 15 +++++++++++++++ .../src/main/scala/example/facade/Os.scala | 10 ++++++++++ .../src/main/scala/example/facade/Path.scala | 12 ++++++++++++ .../src/test/scala/example/MainSpec.scala | 2 +- 6 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/src/main/scala/example/facade/Fs.scala create mode 100644 sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/src/main/scala/example/facade/Os.scala create mode 100644 sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/src/main/scala/example/facade/Path.scala diff --git a/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/esbuild/package.json b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/esbuild/package.json index ed296a9f..3958966b 100644 --- a/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/esbuild/package.json +++ b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/esbuild/package.json @@ -2,10 +2,8 @@ "name": "basic-project", "private": true, "version": "0.0.0", - "dependencies": { - "lodash": "4.17.21" - }, "devDependencies": { - "esbuild": "0.19.5" + "esbuild": "0.19.5", + "lodash": "4.17.21" } } \ No newline at end of file diff --git a/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/src/main/scala/example/Main.scala b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/src/main/scala/example/Main.scala index 157b164d..85f8fcf8 100644 --- a/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/src/main/scala/example/Main.scala +++ b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/src/main/scala/example/Main.scala @@ -1,13 +1,18 @@ package example +import example.facade.Fs import example.facade.Lodash +import example.facade.Os +import example.facade.Path object Main { def main(args: Array[String]): Unit = { - println(testString()) + println(test()) } - def testString(): String = { - Lodash.toUpper("basic-node-project works!") + def test(): String = { + val file = Path.join(Fs.mkdtempSync(s"${Os.tmpdir()}${Path.sep}"), "test") + Fs.writeFileSync(file, Lodash.toUpper("basic-node-project works!"), "utf8") + Fs.readFileSync(file, "utf8") } } diff --git a/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/src/main/scala/example/facade/Fs.scala b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/src/main/scala/example/facade/Fs.scala new file mode 100644 index 00000000..ea44fb97 --- /dev/null +++ b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/src/main/scala/example/facade/Fs.scala @@ -0,0 +1,15 @@ +package example.facade + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport + +@js.native +@JSImport("node:fs", JSImport.Default) +object Fs extends js.Object { + def mkdtempSync(paths: String): String = js.native + + def writeFileSync(file: String, data: String, encoding: String): Unit = + js.native + + def readFileSync(path: String, encoding: String): String = js.native +} diff --git a/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/src/main/scala/example/facade/Os.scala b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/src/main/scala/example/facade/Os.scala new file mode 100644 index 00000000..bba62fd0 --- /dev/null +++ b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/src/main/scala/example/facade/Os.scala @@ -0,0 +1,10 @@ +package example.facade + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport + +@js.native +@JSImport("node:os", JSImport.Default) +object Os extends js.Object { + def tmpdir(): String = js.native +} diff --git a/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/src/main/scala/example/facade/Path.scala b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/src/main/scala/example/facade/Path.scala new file mode 100644 index 00000000..fd2eb649 --- /dev/null +++ b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/src/main/scala/example/facade/Path.scala @@ -0,0 +1,12 @@ +package example.facade + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport + +@js.native +@JSImport("node:path", JSImport.Default) +object Path extends js.Object { + def join(paths: String*): String = js.native + + val sep: String = js.native +} diff --git a/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/src/test/scala/example/MainSpec.scala b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/src/test/scala/example/MainSpec.scala index 430173a5..8c27fed3 100644 --- a/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/src/test/scala/example/MainSpec.scala +++ b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-node-project/src/test/scala/example/MainSpec.scala @@ -7,7 +7,7 @@ class MainSpec extends AnyWordSpec with Matchers { "Main" should { "work" in { - Main.testString() shouldEqual "BASIC-NODE-PROJECT WORKS!" + Main.test() shouldEqual "BASIC-NODE-PROJECT WORKS!" } } } From 80e367c64953bd7f562db7342ebf2a5ceb4841d0 Mon Sep 17 00:00:00 2001 From: Domantas Petrauskas Date: Fri, 3 Nov 2023 22:46:00 +0200 Subject: [PATCH 9/9] Add browser project test to base plugin --- .../basic-browser-project/build.sbt | 28 ++++++++++++++++ .../esbuild/package.json | 10 ++++++ .../basic-browser-project/project/plugins.sbt | 32 +++++++++++++++++++ .../src/main/scala/example/Main.scala | 29 +++++++++++++++++ .../main/scala/example/facade/Lodash.scala | 10 ++++++ .../src/test/scala/example/MainSpec.scala | 18 +++++++++++ .../basic-browser-project/test | 21 ++++++++++++ 7 files changed, 148 insertions(+) create mode 100644 sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/build.sbt create mode 100644 sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/esbuild/package.json create mode 100644 sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/project/plugins.sbt create mode 100644 sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/src/main/scala/example/Main.scala create mode 100644 sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/src/main/scala/example/facade/Lodash.scala create mode 100644 sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/src/test/scala/example/MainSpec.scala create mode 100644 sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/test diff --git a/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/build.sbt b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/build.sbt new file mode 100644 index 00000000..87dd7786 --- /dev/null +++ b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/build.sbt @@ -0,0 +1,28 @@ +enablePlugins(ScalaJSEsbuildPlugin) + +scalaVersion := "2.13.8" + +scalaJSLinkerConfig ~= { + _.withModuleKind(ModuleKind.ESModule) +} + +scalaJSUseMainModuleInitializer := true + +libraryDependencies ++= Seq( + "org.scala-js" %%% "scalajs-dom" % "2.2.0", + "org.scalatest" %%% "scalatest" % "3.2.15" % "test" +) + +lazy val perConfigSettings = Seq( + jsEnv := new org.scalajs.jsenv.jsdomnodejs.JSDOMNodeJSEnv( + org.scalajs.jsenv.jsdomnodejs.JSDOMNodeJSEnv + .Config() + .withEnv( + Map( + "NODE_PATH" -> ((esbuildInstall / crossTarget).value / "node_modules").absolutePath + ) + ) + ) +) +inConfig(Compile)(perConfigSettings) +inConfig(Test)(perConfigSettings) diff --git a/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/esbuild/package.json b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/esbuild/package.json new file mode 100644 index 00000000..df166f05 --- /dev/null +++ b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/esbuild/package.json @@ -0,0 +1,10 @@ +{ + "name": "basic-project", + "private": true, + "version": "0.0.0", + "devDependencies": { + "esbuild": "0.19.5", + "jsdom": "22.1.0", + "lodash": "4.17.21" + } +} \ No newline at end of file diff --git a/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/project/plugins.sbt b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/project/plugins.sbt new file mode 100644 index 00000000..2a068612 --- /dev/null +++ b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/project/plugins.sbt @@ -0,0 +1,32 @@ +val sourcePlugins = sys.props + .get("plugin.version") + .map { version => + println(s"Using plugin(s) version [${version}]") + Seq.empty + } + .getOrElse { + println("Building plugin(s) from source") + Seq( + ProjectRef( + file("../../../../../../"), + "sbt-scalajs-esbuild" + ): ClasspathDep[ProjectReference] + ) + } + +lazy val root = (project in file(".")) + .dependsOn(sourcePlugins: _*) + +if (sourcePlugins.nonEmpty) { + Seq.empty +} else { + val scalaJSEsbuildVersion = sys.props.getOrElse( + "plugin.version", + sys.error("'plugin.version' environment variable is not set") + ) + Seq( + addSbtPlugin("me.ptrdom" % "sbt-scalajs-esbuild" % scalaJSEsbuildVersion) + ) +} + +libraryDependencies += "org.scala-js" %% "scalajs-env-jsdom-nodejs" % "1.1.0" diff --git a/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/src/main/scala/example/Main.scala b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/src/main/scala/example/Main.scala new file mode 100644 index 00000000..441d1a05 --- /dev/null +++ b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/src/main/scala/example/Main.scala @@ -0,0 +1,29 @@ +package example + +import example.facade.Lodash +import org.scalajs.dom +import org.scalajs.dom.document + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport + +object Main { + def main(args: Array[String]): Unit = { + document.addEventListener( + "DOMContentLoaded", + { (_: dom.Event) => + setupUI() + } + ) + } + + def setupUI(): Unit = { + val h1 = document.createElement("h1") + h1.textContent = testString() + document.body.append(h1) + } + + def testString(): String = { + Lodash.toUpper("basic-web-project works!") + } +} diff --git a/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/src/main/scala/example/facade/Lodash.scala b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/src/main/scala/example/facade/Lodash.scala new file mode 100644 index 00000000..75c61f70 --- /dev/null +++ b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/src/main/scala/example/facade/Lodash.scala @@ -0,0 +1,10 @@ +package example.facade + +import scala.scalajs.js +import scala.scalajs.js.annotation.JSImport + +@JSImport("lodash", JSImport.Namespace) +@js.native +object Lodash extends js.Object { + def toUpper(string: String): String = js.native +} diff --git a/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/src/test/scala/example/MainSpec.scala b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/src/test/scala/example/MainSpec.scala new file mode 100644 index 00000000..6b48c990 --- /dev/null +++ b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/src/test/scala/example/MainSpec.scala @@ -0,0 +1,18 @@ +package example + +import org.scalajs.dom.document +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec + +class MainSpec extends AnyWordSpec with Matchers { + + "Main" should { + "work" in { + Main.setupUI() + + document + .querySelector("h1") + .textContent shouldEqual "BASIC-WEB-PROJECT WORKS!" + } + } +} diff --git a/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/test b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/test new file mode 100644 index 00000000..817effc7 --- /dev/null +++ b/sbt-scalajs-esbuild/src/sbt-test/sbt-scalajs-esbuild/basic-browser-project/test @@ -0,0 +1,21 @@ +$ absent esbuild/package-lock.json +> set Test/scalaJSStage := org.scalajs.sbtplugin.Stage.FastOpt +> run +$ exists esbuild/package-lock.json + +$ delete esbuild/package-lock.json +> clean +> set Test/scalaJSStage := org.scalajs.sbtplugin.Stage.FullOpt +> run +$ exists esbuild/package-lock.json + +$ delete esbuild/package-lock.json +> set Test/scalaJSStage := org.scalajs.sbtplugin.Stage.FastOpt +> test +$ exists esbuild/package-lock.json + +$ delete esbuild/package-lock.json +> clean +> set Test/scalaJSStage := org.scalajs.sbtplugin.Stage.FullOpt +> test +$ exists esbuild/package-lock.json