Skip to content

Commit 67db91c

Browse files
authored
Add support for SmallModulesFor style (#894)
1 parent 7c39c93 commit 67db91c

File tree

10 files changed

+104
-5
lines changed

10 files changed

+104
-5
lines changed

modules/cli-options/src/main/scala/scala/cli/commands/ScalaJsOptions.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,12 @@ final case class ScalaJsOptions(
4646
@HelpMessage("Avoid lets and consts when using vars has the same observable semantics.")
4747
jsAvoidLetsAndConsts: Option[Boolean] = None,
4848
@Group("Scala.js")
49-
@HelpMessage("The Scala.js module split style: fewestmodules, smallestmodules")
49+
@HelpMessage("The Scala.js module split style: fewestmodules, smallestmodules, smallmodulesfor")
5050
jsModuleSplitStyle: Option[String] = None,
5151
@Group("Scala.js")
52+
@HelpMessage("Create as many small modules as possible for the classes in the passed packages and their subpackages.")
53+
jsSmallModuleForPackage: List[String] = Nil,
54+
@Group("Scala.js")
5255
@HelpMessage("The Scala.js ECMA Script version: es5_1, es2015, es2016, es2017, es2018, es2019, es2020, es2021")
5356
jsEsVersion: Option[String] = None,
5457

modules/cli/src/main/scala/scala/cli/commands/util/SharedOptionsUtil.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ object SharedOptionsUtil {
4343
avoidClasses = jsAvoidClasses,
4444
avoidLetsAndConsts = jsAvoidLetsAndConsts,
4545
moduleSplitStyleStr = jsModuleSplitStyle,
46+
smallModuleForPackage = jsSmallModuleForPackage,
4647
esVersionStr = jsEsVersion
4748
)
4849
}

modules/directives/src/main/scala/scala/build/preprocessing/directives/UsingScalaJsOptionsDirectiveHandler.scala

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ case object UsingScalaJsOptionsDirectiveHandler extends UsingDirectiveHandler {
2020
|
2121
|`//> using jsModuleKind` _value_
2222
|
23+
|`//> using jsSmallModuleForPackage` _value1_, _value2_
24+
|
2325
|`//> using jsCheckIr` _true|false_
2426
|
2527
|`//> using jsEmitSourceMaps` _true|false_
@@ -50,6 +52,7 @@ case object UsingScalaJsOptionsDirectiveHandler extends UsingDirectiveHandler {
5052
"jsModuleKind",
5153
"jsCheckIr",
5254
"jsEmitSourceMaps",
55+
"jsSmallModuleForPackage",
5356
"jsDom",
5457
"jsHeader",
5558
"jsAllowBigIntsForLongs",
@@ -71,7 +74,7 @@ case object UsingScalaJsOptionsDirectiveHandler extends UsingDirectiveHandler {
7174
groupedValues.scopedStringValues.headOption.map(_.positioned.value)
7275

7376
override def getSupportedTypes(key: String) = key match {
74-
case "jsVersion" | "jsHeader" | "jsModuleKind" | "jsMode" | "jsModuleSplitStyleStr" | "jsEsVersionStr" =>
77+
case "jsVersion" | "jsHeader" | "jsModuleKind" | "jsMode" | "jsModuleSplitStyleStr" | "jsEsVersionStr" | "jsSmallModuleForPackage" =>
7578
Set(UsingDirectiveValueKind.STRING)
7679
case "jsCheckIr" | "jsAllowBigIntsForLongs" | "jsEmitSourceMaps" | "jsDom" | "jsAvoidClasses" | "jsAvoidLetsAndConsts" =>
7780
Set(UsingDirectiveValueKind.BOOLEAN, UsingDirectiveValueKind.EMPTY)
@@ -82,6 +85,8 @@ case object UsingScalaJsOptionsDirectiveHandler extends UsingDirectiveHandler {
8285
UsingDirectiveValueNumberBounds(1, 1)
8386
case "jsCheckIr" | "jsAllowBigIntsForLongs" | "jsEmitSourceMaps" | "jsDom" | "jsAvoidClasses" | "jsAvoidLetsAndConsts" =>
8487
UsingDirectiveValueNumberBounds(0, 1)
88+
case "jsSmallModuleForPackage" =>
89+
UsingDirectiveValueNumberBounds(1)
8590
}
8691

8792
def handleValues(
@@ -102,6 +107,11 @@ case object UsingScalaJsOptionsDirectiveHandler extends UsingDirectiveHandler {
102107
case "jsEmitSourceMaps" => BuildOptions(scalaJsOptions =
103108
ScalaJsOptions(emitSourceMaps = getBooleanValue(groupedValues))
104109
)
110+
case "jsSmallModuleForPackage" => BuildOptions(scalaJsOptions =
111+
ScalaJsOptions(smallModuleForPackage =
112+
groupedValues.scopedStringValues.map(_.positioned.value).toList
113+
)
114+
)
105115
case "jsDom" =>
106116
BuildOptions(scalaJsOptions = ScalaJsOptions(dom = getBooleanOption(groupedValues)))
107117
case "jsHeader" =>

modules/integration/src/test/scala/scala/cli/integration/PackageTestDefinitions.scala

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,49 @@ abstract class PackageTestDefinitions(val scalaVersionOpt: Option[String])
271271
expect(output == message)
272272
}
273273
}
274+
def smallModulesJsTest(): Unit = {
275+
val fileName = "Hello.scala"
276+
val message = "Hello World from JS"
277+
val inputs = TestInputs(
278+
Seq(
279+
os.rel / fileName ->
280+
s"""|//> using jsModuleKind "es"
281+
|//> using jsModuleSplitStyleStr "smallmodulesfor"
282+
|//> using jsSmallModuleForPackage "test"
283+
|
284+
|package test
285+
|
286+
|case class Foo(bar: String)
287+
|
288+
|object Hello extends App {
289+
| println(Foo("$message").bar)
290+
|}
291+
|""".stripMargin
292+
)
293+
)
294+
val destDir = fileName.stripSuffix(".scala")
295+
inputs.fromRoot { root =>
296+
os.proc(
297+
TestUtil.cli,
298+
"package",
299+
extraOptions,
300+
fileName,
301+
"--js",
302+
"-o",
303+
destDir
304+
).call(
305+
cwd = root,
306+
stdin = os.Inherit,
307+
stdout = os.Inherit
308+
)
309+
310+
val launcher = root / destDir / "main.js"
311+
val nodePath = TestUtil.fromPath("node").getOrElse("node")
312+
os.write(root / "package.json", "{\n\n \"type\": \"module\"\n\n}") // enable es module
313+
val output = os.proc(nodePath, launcher.toString).call(cwd = root).out.text().trim
314+
expect(output == message)
315+
}
316+
}
274317

275318
def jsHeaderTest(): Unit = {
276319
val fileName = "Hello.scala"
@@ -312,6 +355,9 @@ abstract class PackageTestDefinitions(val scalaVersionOpt: Option[String])
312355
test("multi modules js") {
313356
multiModulesJsTest()
314357
}
358+
test("small modules js") {
359+
smallModulesJsTest()
360+
}
315361
test("js header in release mode") {
316362
jsHeaderTest()
317363
}

modules/options/src/main/scala/scala/build/internal/ScalaJsLinkerConfig.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ final case class ScalaJsLinkerConfig(
66
checkIR: Boolean = false,
77
sourceMap: Boolean = true,
88
moduleSplitStyle: String = ScalaJsLinkerConfig.ModuleSplitStyle.FewestModules,
9+
smallModuleForPackage: List[String] = Nil,
910
esFeatures: ScalaJsLinkerConfig.ESFeatures = ScalaJsLinkerConfig.ESFeatures(),
1011
jsHeader: Option[String] = None,
1112
prettyPrint: Boolean = false,
@@ -24,6 +25,11 @@ final case class ScalaJsLinkerConfig(
2425
Nil
2526
val moduleKindArgs = Seq("--moduleKind", moduleKind)
2627
val moduleSplitStyleArgs = Seq("--moduleSplitStyle", moduleSplitStyle)
28+
val smallModuleForPackageArgs =
29+
if (smallModuleForPackage.nonEmpty)
30+
Seq("--smallModuleForPackages", smallModuleForPackage.mkString(","))
31+
else
32+
Nil
2733
val esFeaturesArgs =
2834
if (esFeatures.esVersion == ScalaJsLinkerConfig.ESVersion.ES2015)
2935
Seq("--es2015")
@@ -42,6 +48,7 @@ final case class ScalaJsLinkerConfig(
4248
semanticsArgs,
4349
moduleKindArgs,
4450
moduleSplitStyleArgs,
51+
smallModuleForPackageArgs,
4552
esFeaturesArgs,
4653
checkIRArgs,
4754
sourceMapArgs,
@@ -64,6 +71,7 @@ object ScalaJsLinkerConfig {
6471
object ModuleSplitStyle {
6572
val FewestModules = "FewestModules"
6673
val SmallestModules = "SmallestModules"
74+
val SmallModulesFor = "SmallModulesFor"
6775
}
6876

6977
final case class ESFeatures(

modules/options/src/main/scala/scala/build/options/ScalaJsOptions.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ final case class ScalaJsOptions(
2121
avoidClasses: Option[Boolean] = None,
2222
avoidLetsAndConsts: Option[Boolean] = None,
2323
moduleSplitStyleStr: Option[String] = None,
24+
smallModuleForPackage: List[String] = Nil,
2425
esVersionStr: Option[String] = None,
2526
noOpt: Option[Boolean] = None
2627
) {
@@ -59,6 +60,7 @@ final case class ScalaJsOptions(
5960
.map {
6061
case "fewestmodules" => ScalaJsLinkerConfig.ModuleSplitStyle.FewestModules
6162
case "smallestmodules" => ScalaJsLinkerConfig.ModuleSplitStyle.SmallestModules
63+
case "smallmodulesfor" => ScalaJsLinkerConfig.ModuleSplitStyle.SmallModulesFor
6264
case unknown =>
6365
logger.message(
6466
s"Warning: unrecognized argument: $unknown for --js-module-split-style parameter, use default value: fewestmodules"
@@ -130,6 +132,7 @@ final case class ScalaJsOptions(
130132
checkIr.getOrElse(false), // meh
131133
emitSourceMaps,
132134
moduleSplitStyle(logger),
135+
smallModuleForPackage,
133136
esFeatures,
134137
header
135138
)

project/deps.sc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ object InternalDeps {
4444
object Versions {
4545
def mill = os.read(os.pwd / ".mill-version").trim
4646
def lefouMillwRef = "166bcdf5741de8569e0630e18c3b2ef7e252cd96"
47-
def scalaJsCli = "1.1.1-sc3.1"
47+
def scalaJsCli = "1.1.1-sc4.1"
4848
}
4949
}
5050

website/docs/guides/scala-js.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ node hello.js
5252
Hello World from Scala.js
5353
-->
5454

55-
### Module Split Style - Smallest Modules
55+
### Module Split Style
56+
57+
### Smallest Modules
5658

5759
Passing `--js-module-split-style smallestmodules` to the `package` sub-command creates js modules that are as small as possible.
5860
Scala.js linker generates a lot of js modules, which are copied to the `output` directory.
@@ -78,6 +80,26 @@ node hello/main.js
7880
Hello World from Scala.js
7981
-->
8082

83+
### Small Modules For
84+
85+
Passing `--js-module-split-style smallestmodules` to the `package` sub-command creates many small modules as possibles for the classes in the listed packages (and their subpackages). To define packages use `jsSmallModuleForPackage` parameter.
86+
87+
```scala title=SmallestModules.scala
88+
//> using jsModuleKind "es"
89+
//> using jsModuleSplitStyleStr "smallmodulesfor"
90+
//> using jsSmallModuleForPackage "com.example.test", "com.example.example""
91+
92+
package com.example.test
93+
94+
import scala.scalajs.js
95+
96+
case class Foo(txt: String)
97+
98+
object Hello extends App {
99+
println(Foo("Hello World from Scala.js").txt)
100+
}
101+
```
102+
81103
## Emit source maps
82104

83105
Passing `--js-emit-source-maps` to the `package` sub-command emits source maps alongside js files. To set the destination path of source maps, pass `--js-source-maps-path` flag with the argument.

website/docs/reference/cli-options.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1095,7 +1095,11 @@ Avoid lets and consts when using vars has the same observable semantics.
10951095

10961096
#### `--js-module-split-style`
10971097

1098-
The Scala.js module split style: fewestmodules, smallestmodules
1098+
The Scala.js module split style: fewestmodules, smallestmodules, smallmodulesfor
1099+
1100+
#### `--js-small-module-for-package`
1101+
1102+
Create as many small modules as possible for the classes in the passed packages and their subpackages.
10991103

11001104
#### `--js-es-version`
11011105

website/docs/reference/directives.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ Add Scala.js options
192192

193193
`//> using jsModuleKind` _value_
194194

195+
`//> using jsSmallModuleForPackage` _value1_, _value2_
196+
195197
`//> using jsCheckIr` _true|false_
196198

197199
`//> using jsEmitSourceMaps` _true|false_

0 commit comments

Comments
 (0)