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

Add support for scalaJSOutputPatterns #2233

Merged
merged 2 commits into from
Jan 5, 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
24 changes: 12 additions & 12 deletions scalajslib/src/mill/scalajslib/ScalaJSModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,7 @@ import mill.scalalib.Lib.resolveDependencies
import mill.scalalib.{Dep, DepSyntax, Lib, TestModule}
import mill.testrunner.TestRunner
import mill.define.{Command, Target, Task}
import mill.scalajslib.api.{
ESFeatures,
ESVersion,
JsEnvConfig,
ModuleKind,
ModuleSplitStyle,
Report
}
import mill.scalajslib.api._
import mill.scalajslib.internal.ScalaJSUtils.getReportMainFilePathRef
import mill.scalajslib.worker.{ScalaJSWorker, ScalaJSWorkerExternalModule}

Expand Down Expand Up @@ -121,7 +114,8 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer =>
sourceMap = scalaJSSourceMap(),
moduleKind = moduleKind(),
esFeatures = esFeatures(),
moduleSplitStyle = moduleSplitStyle()
moduleSplitStyle = moduleSplitStyle(),
outputPatterns = scalaJSOutputPatterns()
)
}

Expand Down Expand Up @@ -161,7 +155,8 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer =>
sourceMap: Boolean,
moduleKind: ModuleKind,
esFeatures: ESFeatures,
moduleSplitStyle: ModuleSplitStyle
moduleSplitStyle: ModuleSplitStyle,
outputPatterns: OutputPatterns
)(implicit ctx: mill.api.Ctx): Result[Report] = {
val outputPath = ctx.dest

Expand All @@ -186,7 +181,8 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer =>
sourceMap = sourceMap,
moduleKind = moduleKind,
esFeatures = esFeatures,
moduleSplitStyle = moduleSplitStyle
moduleSplitStyle = moduleSplitStyle,
outputPatterns = outputPatterns
)
}

Expand Down Expand Up @@ -238,6 +234,9 @@ trait ScalaJSModule extends scalalib.ScalaModule { outer =>
/** Whether to emit a source map. */
def scalaJSSourceMap: Target[Boolean] = T { true }

/** Name patterns for output. */
def scalaJSOutputPatterns: Target[OutputPatterns] = T { OutputPatterns.Defaults }

@internal
override def bspBuildTargetData: Task[Option[(String, AnyRef)]] = T.task {
Some((
Expand Down Expand Up @@ -281,7 +280,8 @@ trait TestScalaJSModule extends ScalaJSModule with TestModule {
sourceMap = scalaJSSourceMap(),
moduleKind = moduleKind(),
esFeatures = esFeatures(),
moduleSplitStyle = moduleSplitStyle()
moduleSplitStyle = moduleSplitStyle(),
outputPatterns = scalaJSOutputPatterns()
)
}

Expand Down
90 changes: 90 additions & 0 deletions scalajslib/src/mill/scalajslib/api/ScalaJSApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,93 @@ object JsEnvConfig {
autoExit: Boolean
) extends JsEnvConfig
}

class OutputPatterns private (
val jsFile: String,
val sourceMapFile: String,
val moduleName: String,
val jsFileURI: String,
val sourceMapURI: String
) {

/** Pattern for the JS file name (the file containing the module's code). */
def withJSFile(jsFile: String): OutputPatterns =
copy(jsFile = jsFile)

/** Pattern for the file name of the source map file of the JS file. */
def withSourceMapFile(sourceMapFile: String): OutputPatterns =
copy(sourceMapFile = sourceMapFile)

/** Pattern for the module name (the string used to import a module). */
def withModuleName(moduleName: String): OutputPatterns =
copy(moduleName = moduleName)

/** Pattern for the "file" field in the source map. */
def withJSFileURI(jsFileURI: String): OutputPatterns =
copy(jsFileURI = jsFileURI)

/** Pattern for the source map URI in the JS file. */
def withSourceMapURI(sourceMapURI: String): OutputPatterns =
copy(sourceMapURI = sourceMapURI)

override def toString(): String = {
s"""OutputPatterns(
| jsFile = $jsFile,
| sourceMapFile = $sourceMapFile,
| moduleName = $moduleName,
| jsFileURI = $jsFileURI,
| sourceMapURI = $sourceMapURI,
|)""".stripMargin
}

private def copy(
jsFile: String = jsFile,
sourceMapFile: String = sourceMapFile,
moduleName: String = moduleName,
jsFileURI: String = jsFileURI,
sourceMapURI: String = sourceMapURI
): OutputPatterns = {
new OutputPatterns(jsFile, sourceMapFile, moduleName, jsFileURI, sourceMapURI)
}
}

object OutputPatterns {

/** Default [[OutputPatterns]]; equivalent to `fromJSFile("%s.js")`. */
val Defaults: OutputPatterns = fromJSFile("%s.js")

/**
* Creates [[OutputPatterns]] from a JS file pattern.
*
* Other patterns are derived from the JS file pattern as follows:
* - `sourceMapFile`: ".map" is appended.
* - `moduleName`: "./" is prepended (relative path import).
* - `jsFileURI`: relative URI (same as the provided pattern).
* - `sourceMapURI`: relative URI (same as `sourceMapFile`).
*/
def fromJSFile(jsFile: String): OutputPatterns = {
new OutputPatterns(
jsFile = jsFile,
sourceMapFile = s"$jsFile.map",
moduleName = s"./$jsFile",
jsFileURI = jsFile,
sourceMapURI = s"$jsFile.map"
)
}

private def apply(
jsFile: String,
sourceMapFile: String,
moduleName: String,
jsFileURI: String,
sourceMapURI: String
): OutputPatterns = new OutputPatterns(
jsFile,
sourceMapFile,
moduleName,
jsFileURI,
sourceMapURI
)

implicit val rw: RW[OutputPatterns] = macroRW[OutputPatterns]
}
16 changes: 14 additions & 2 deletions scalajslib/src/mill/scalajslib/worker/ScalaJSWorker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,16 @@ private[scalajslib] class ScalaJSWorker extends AutoCloseable {
)
}

private def toWorkerApi(outputPatterns: api.OutputPatterns): workerApi.OutputPatterns = {
workerApi.OutputPatterns(
jsFile = outputPatterns.jsFile,
sourceMapFile = outputPatterns.sourceMapFile,
moduleName = outputPatterns.moduleName,
jsFileURI = outputPatterns.jsFileURI,
sourceMapURI = outputPatterns.sourceMapURI
)
}

def link(
toolsClasspath: Agg[mill.PathRef],
sources: Agg[os.Path],
Expand All @@ -140,7 +150,8 @@ private[scalajslib] class ScalaJSWorker extends AutoCloseable {
sourceMap: Boolean,
moduleKind: api.ModuleKind,
esFeatures: api.ESFeatures,
moduleSplitStyle: api.ModuleSplitStyle
moduleSplitStyle: api.ModuleSplitStyle,
outputPatterns: api.OutputPatterns
)(implicit ctx: Ctx.Home): Result[api.Report] = {
bridge(toolsClasspath).link(
sources = sources.items.map(_.toIO).toArray,
Expand All @@ -154,7 +165,8 @@ private[scalajslib] class ScalaJSWorker extends AutoCloseable {
sourceMap = sourceMap,
moduleKind = toWorkerApi(moduleKind),
esFeatures = toWorkerApi(esFeatures),
moduleSplitStyle = toWorkerApi(moduleSplitStyle)
moduleSplitStyle = toWorkerApi(moduleSplitStyle),
outputPatterns = toWorkerApi(outputPatterns)
) match {
case Right(report) => Result.Success(fromWorkerApi(report))
case Left(message) => Result.Failure(message)
Expand Down
51 changes: 51 additions & 0 deletions scalajslib/test/src/OutputPatternsTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package mill.scalajslib

import mill._
import mill.define.Discover
import mill.scalajslib.api._
import mill.util.{TestEvaluator, TestUtil}
import utest._

object OutputPatternsTests extends TestSuite {
val workspacePath = TestUtil.getOutPathStatic() / "hello-js-world"

object OutputPatternsModule extends TestUtil.BaseModule {

object outputPatternsModule extends ScalaJSModule {
override def millSourcePath = workspacePath
override def scalaVersion = sys.props.getOrElse("TEST_SCALA_2_13_VERSION", ???)
override def scalaJSVersion = "1.12.0"
override def moduleKind = ModuleKind.CommonJSModule
override def scalaJSOutputPatterns = OutputPatterns.fromJSFile("%s.mjs")
}

override lazy val millDiscover = Discover[this.type]
}

val millSourcePath = os.pwd / "scalajslib" / "test" / "resources" / "hello-js-world"

val evaluator = TestEvaluator.static(OutputPatternsModule)

val tests: Tests = Tests {
prepareWorkspace()

test("output patterns") {
val Right((report, _)) =
evaluator(OutputPatternsModule.outputPatternsModule.fastLinkJS)
val publicModules = report.publicModules.toSeq
assert(publicModules.length == 1)
val main = publicModules(0)
assert(main.jsFileName == "main.mjs")
assert(os.exists(report.dest.path / "main.mjs"))
assert(main.sourceMapName == Some("main.mjs.map"))
assert(os.exists(report.dest.path / "main.mjs.map"))
}
}

def prepareWorkspace(): Unit = {
os.remove.all(workspacePath)
os.makeDir.all(workspacePath / os.up)
os.copy(millSourcePath, workspacePath)
}

}
11 changes: 10 additions & 1 deletion scalajslib/worker-api/src/ScalaJSWorkerApi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ private[scalajslib] trait ScalaJSWorkerApi {
sourceMap: Boolean,
moduleKind: ModuleKind,
esFeatures: ESFeatures,
moduleSplitStyle: ModuleSplitStyle
moduleSplitStyle: ModuleSplitStyle,
outputPatterns: OutputPatterns
): Either[String, Report]

def run(config: JsEnvConfig, report: Report): Unit
Expand Down Expand Up @@ -102,3 +103,11 @@ private[scalajslib] object ModuleSplitStyle {
case object SmallestModules extends ModuleSplitStyle
final case class SmallModulesFor(packages: List[String]) extends ModuleSplitStyle
}

private[scalajslib] final case class OutputPatterns(
jsFile: String,
sourceMapFile: String,
moduleName: String,
jsFileURI: String,
sourceMapURI: String
)
3 changes: 2 additions & 1 deletion scalajslib/worker/0.6/src/ScalaJSWorkerImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ class ScalaJSWorkerImpl extends ScalaJSWorkerApi {
sourceMap: Boolean, // ignored in 0.6
moduleKind: ModuleKind,
esFeatures: ESFeatures,
moduleSplitStyle: ModuleSplitStyle // ignored in 0.6
moduleSplitStyle: ModuleSplitStyle, // ignored in 0.6
outputPatterns: OutputPatterns // ignored in 0.6
Comment on lines +86 to +87
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense to focus on dropping 0.6 support at this point. See #2218.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is meant to go very soon, so we don't want to invest time on it :)

): Either[String, Report] = {
val linker = ScalaJSLinker.reuseOrCreate(LinkerInput(
isFullLinkJS = isFullLinkJS,
Expand Down
59 changes: 38 additions & 21 deletions scalajslib/worker/1/src/ScalaJSWorkerImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import org.scalajs.linker.interface.{
ESFeatures => ScalaJSESFeatures,
ESVersion => ScalaJSESVersion,
ModuleKind => ScalaJSModuleKind,
OutputPatterns => ScalaJSOutputPatterns,
Report => ScalaJSReport,
ModuleSplitStyle => _,
_
Expand All @@ -37,6 +38,7 @@ class ScalaJSWorkerImpl extends ScalaJSWorkerApi {
moduleKind: ModuleKind,
esFeatures: ESFeatures,
moduleSplitStyle: ModuleSplitStyle,
outputPatterns: OutputPatterns,
dest: File
)
private def minorIsGreaterThanOrEqual(number: Int) = ScalaJSVersions.current match {
Expand Down Expand Up @@ -98,31 +100,14 @@ class ScalaJSWorkerImpl extends ScalaJSWorkerApi {
else withESVersion_1_5_minus(scalaJSESFeatures)

val useClosure = input.isFullLinkJS && input.moduleKind != ModuleKind.ESModule
val partialConfig = StandardConfig()
var partialConfig = StandardConfig()
.withOptimizer(input.optimizer)
.withClosureCompilerIfAvailable(useClosure)
.withSemantics(semantics)
.withModuleKind(scalaJSModuleKind)
.withESFeatures(scalaJSESFeatures)
.withSourceMap(input.sourceMap)

// Separating ModuleSplitStyle in a standalone object avoids
// early classloading which fails in Scala.js versions where
// the classes don't exist
object ScalaJSModuleSplitStyle {
import org.scalajs.linker.interface.ModuleSplitStyle
object SmallModulesFor {
def apply(packages: List[String]): ModuleSplitStyle =
ModuleSplitStyle.SmallModulesFor(packages)
}
object FewestModules {
def apply(): ModuleSplitStyle = ModuleSplitStyle.FewestModules
}
object SmallestModules {
def apply(): ModuleSplitStyle = ModuleSplitStyle.SmallestModules
}
}

def withModuleSplitStyle_1_3_plus(config: StandardConfig): StandardConfig = {
config.withModuleSplitStyle(
input.moduleSplitStyle match {
Expand All @@ -147,11 +132,24 @@ class ScalaJSWorkerImpl extends ScalaJSWorkerApi {
config
}

val config =
val withModuleSplitStyle =
if (minorIsGreaterThanOrEqual(3)) withModuleSplitStyle_1_3_plus(partialConfig)
else withModuleSplitStyle_1_2_minus(partialConfig)

StandardImpl.clearableLinker(config)
val withOutputPatterns =
if (minorIsGreaterThanOrEqual(3))
withModuleSplitStyle
.withOutputPatterns(
ScalaJSOutputPatterns.Defaults
.withJSFile(input.outputPatterns.jsFile)
.withJSFileURI(input.outputPatterns.jsFileURI)
.withModuleName(input.outputPatterns.moduleName)
.withSourceMapFile(input.outputPatterns.sourceMapFile)
.withSourceMapURI(input.outputPatterns.sourceMapURI)
)
else withModuleSplitStyle

StandardImpl.clearableLinker(withOutputPatterns)
}
}
def link(
Expand All @@ -166,7 +164,8 @@ class ScalaJSWorkerImpl extends ScalaJSWorkerApi {
sourceMap: Boolean,
moduleKind: ModuleKind,
esFeatures: ESFeatures,
moduleSplitStyle: ModuleSplitStyle
moduleSplitStyle: ModuleSplitStyle,
outputPatterns: OutputPatterns
): Either[String, Report] = {
// On Scala.js 1.2- we want to use the legacy mode either way since
// the new mode is not supported and in tests we always use legacy = false
Expand All @@ -179,6 +178,7 @@ class ScalaJSWorkerImpl extends ScalaJSWorkerApi {
moduleKind = moduleKind,
esFeatures = esFeatures,
moduleSplitStyle = moduleSplitStyle,
outputPatterns = outputPatterns,
dest = dest
))
val cache = StandardImpl.irFileCache().newCache
Expand Down Expand Up @@ -339,3 +339,20 @@ class ScalaJSWorkerImpl extends ScalaJSWorkerApi {
Seq(input)
}
}

// Separating ModuleSplitStyle in a standalone object avoids
// early classloading which fails in Scala.js versions where
// the classes don't exist
object ScalaJSModuleSplitStyle {
import org.scalajs.linker.interface.ModuleSplitStyle
object SmallModulesFor {
def apply(packages: List[String]): ModuleSplitStyle =
ModuleSplitStyle.SmallModulesFor(packages)
}
object FewestModules {
def apply(): ModuleSplitStyle = ModuleSplitStyle.FewestModules
}
object SmallestModules {
def apply(): ModuleSplitStyle = ModuleSplitStyle.SmallestModules
}
}