Skip to content

Commit

Permalink
Per Scala.js module esbuild config (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
ptrdom authored Nov 16, 2023
1 parent e421306 commit 5467de5
Show file tree
Hide file tree
Showing 20 changed files with 352 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ object EsbuildWebScripts {
|
| const ctx = await esbuild.context({
| ...esbuildOptions(
| 'browser',
| entryPoints,
| outDirectory,
| outputFilesDirectory,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 {
Expand All @@ -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")
)
Expand Down Expand Up @@ -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
Expand All @@ -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),
Expand All @@ -132,8 +145,9 @@ object ScalaJSEsbuildWebPlugin extends AutoPlugin {
|${EsbuildWebScripts.transformHtmlEntryPoints}
|
|const metaFilePromise = bundle(
| ${EsbuildScalaJSModuleConfiguration.EsbuildPlatform.Browser.jsValue},
| $entryPointsJsArray,
| ${s"'$relativeOutputDirectory'"},
| $relativeOutputDirectoryJs,
| 'assets',
| $hashOutputFiles,
| $minify,
Expand All @@ -144,7 +158,7 @@ object ScalaJSEsbuildWebPlugin extends AutoPlugin {
| .then((metaFile) => {
| transformHtmlEntryPoints(
| $htmlEntryPointsJsArray,
| ${s"'$relativeOutputDirectory'"},
| $relativeOutputDirectoryJs,
| metaFile
| );
| });
Expand All @@ -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
Expand All @@ -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),
Expand All @@ -200,7 +221,7 @@ object ScalaJSEsbuildWebPlugin extends AutoPlugin {
|
|serve(
| $entryPointsJsArray,
| ${s"'$relativeOutputDirectory'"},
| $relativeOutputDirectoryJs,
| 'assets',
| 'sbt-scalajs-esbuild-serve-meta.json',
| 8001,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package scalajsesbuild

import scalajsesbuild.EsbuildScalaJSModuleConfiguration.EsbuildPlatform

final class EsbuildScalaJSModuleConfiguration(val platform: EsbuildPlatform) {
override def toString: String = {
s"EsbuildScalaJSModuleConfiguration($platform)"
}
}

object EsbuildScalaJSModuleConfiguration {
sealed trait EsbuildPlatform {
def jsValue: String = s"'${toString.toLowerCase}'"
}
object EsbuildPlatform {
case object Browser extends EsbuildPlatform
case object Node extends EsbuildPlatform
case object Neutral extends EsbuildPlatform
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ object EsbuildScripts {
private[scalajsesbuild] def esbuildOptions = {
// language=JS
"""const esbuildOptions = (
| platform,
| entryPoints,
| outDirectory,
| outputFilesDirectory,
Expand Down Expand Up @@ -64,6 +65,7 @@ object EsbuildScripts {
| }
|
| return {
| platform: platform,
| entryPoints: entryPoints,
| bundle: true,
| outdir: path.normalize(outDirectory),
Expand Down Expand Up @@ -93,6 +95,7 @@ object EsbuildScripts {
private[scalajsesbuild] def bundle = {
// language=JS
"""const bundle = async (
| platform,
| entryPoints,
| outDirectory,
| outputFilesDirectory,
Expand All @@ -105,6 +108,7 @@ object EsbuildScripts {
|
| const result = await esbuild.build(
| esbuildOptions(
| platform,
| entryPoints,
| outDirectory,
| outputFilesDirectory,
Expand All @@ -113,10 +117,45 @@ object EsbuildScripts {
| )
| );
|
| fs.writeFileSync(metaFileName, JSON.stringify(result.metafile));
| if (metaFileName) {
| fs.writeFileSync(metaFileName, JSON.stringify(result.metafile));
| }
|
| 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,
| outputFilesDirectory,
| hashOutputFiles,
| minify
| )
| .then((metafile) => {
| return {[platform]: metafile};
| });
| acc.push(platformMetafilePromise);
| return acc;
| }, [])
| )
| .then((results) => results.reduce((acc, result) => ({...acc, ...result}) , {}));
|};
|""".stripMargin
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand Down Expand Up @@ -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
Expand All @@ -58,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,
Expand All @@ -82,6 +84,21 @@ object ScalaJSEsbuildPlugin extends AutoPlugin {
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" /
Expand Down Expand Up @@ -193,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.jsValue}:${entryPoints.map("'" + _ + "'").mkString("[", ",", "]")}"
} + "}"
val targetDirectory = (esbuildInstall / crossTarget).value
val outputDirectory =
(stageTask / esbuildBundle / crossTarget).value
Expand All @@ -205,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
Expand All @@ -218,13 +242,14 @@ object ScalaJSEsbuildPlugin extends AutoPlugin {
|
|${EsbuildScripts.bundle}
|
|bundle(
| ${entryPoints.map("'" + _ + "'").mkString("[", ",", "]")},
| ${s"'$relativeOutputDirectory'"},
|${EsbuildScripts.bundleByPlatform}
|
|bundleByPlatform(
| $entryPointsByPlatformJs,
| $relativeOutputDirectoryJs,
| null,
| false,
| $minify,
| 'sbt-scalajs-esbuild-bundle-meta.json'
| $minify
|);
|""".stripMargin
},
Expand Down
30 changes: 30 additions & 0 deletions sbt-scalajs-esbuild/src/main/scala/scalajsesbuild/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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"
}
}
Loading

0 comments on commit 5467de5

Please sign in to comment.