Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Per Scala.js module esbuild config #49

Merged
merged 10 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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