-
-
Notifications
You must be signed in to change notification settings - Fork 11
/
build.sbt
233 lines (209 loc) · 8.64 KB
/
build.sbt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
import org.scalajs.linker.interface.ModuleSplitStyle
import scala.sys.process.Process
ThisBuild / version := "0.1.0"
ThisBuild / scalaVersion := Versions.Scala_3
lazy val root = project.in(file("."))
.aggregate(client, server)
.settings(
name := "Laminar Demo"
)
.settings(commonSettings)
.settings(noPublish)
lazy val shared = crossProject(JSPlatform, JVMPlatform)
.in(file("."))
.enablePlugins(BuildInfoPlugin)
.settings(commonSettings)
.settings(
// sbt-BuildInfo plugin can write any (simple) data available in sbt at
// compile time to a `case class BuildInfo` that it makes available at runtime.
buildInfoKeys := Seq[BuildInfoKey](scalaVersion, sbtVersion, BuildInfoKey("laminarVersion" -> Versions.Laminar)),
// The BuildInfo case class is located in target/scala<version>/src_managed,
// and with this setting, you'll need to `import com.raquo.buildinfo.BuildInfo`
// to use it.
buildInfoPackage := "com.raquo.buildinfo"
// Because we add BuildInfo to the `shared` project, this will be available
// on both the client and the server, but you can also make it e.g. server-only.
)
.settings(
libraryDependencies ++= List(
// JSON codec
"io.bullet" %%% "borer-core" % Versions.Borer,
"io.bullet" %%% "borer-derivation" % Versions.Borer,
)
)
.jvmSettings(
libraryDependencies ++= List(
// This dependency lets us put @JSExportAll and similar Scala.js
// annotations on data structures shared between JS and JVM.
// With this library, on the JVM, these annotations compile to
// no-op, which is exactly what we need.
"org.scala-js" %% "scalajs-stubs" % Versions.ScalaJsStubs
)
)
lazy val server = project
.in(file("./server"))
.settings(commonSettings)
.settings(
libraryDependencies ++= List(
// Effect library providing the IO type, used as a better alternative to scala.Future
"org.typelevel" %% "cats-effect" % Versions.CatsEffect,
// Http4s web server framework
"org.http4s" %% "http4s-ember-server" % Versions.Http4s,
"org.http4s" %% "http4s-dsl" % Versions.Http4s,
// Logging
"org.typelevel" %% "log4cats-slf4j" % Versions.Log4Cats,
"ch.qos.logback" % "logback-classic" % Versions.Logback,
// Http4s HTTP client to fetch data from the weather API
"org.http4s" %% "http4s-ember-client" % Versions.Http4s,
// XML decoder (to parse weather API XMLs)
"ru.tinkoff" %% "phobos-core" % Versions.Phobos,
)
)
.settings(
assembly / mainClass := Some("com.raquo.server.Server"),
assembly / assemblyJarName := "app.jar",
// Gets rid of "(server / assembly) deduplicate: different file contents found in the following" errors
// https://stackoverflow.com/questions/54834125/sbt-assembly-deduplicate-module-info-class
assembly / assemblyMergeStrategy := {
case path if path.endsWith("module-info.class") => MergeStrategy.discard
case path =>
val oldStrategy = (assembly / assemblyMergeStrategy).value
oldStrategy(path)
}
)
.dependsOn(shared.jvm)
lazy val client = project
.in(file("./client"))
.enablePlugins(ScalaJSPlugin)
.settings(commonSettings)
.settings(
libraryDependencies ++= List(
"com.raquo" %%% "laminar" % Versions.Laminar,
// "com.raquo" %%% "airstream" % "17.0.0-M2-SNAPSHOT",
"com.raquo" %%% "laminar-shoelace" % Versions.LaminarShoelace,
"com.raquo" %%% "waypoint" % Versions.Waypoint,
"be.doeraene" %%% "web-components-ui5" % Versions.UI5
),
scalaJSLinkerConfig ~= {
_.withModuleKind(ModuleKind.ESModule)
// .withModuleSplitStyle(
// ModuleSplitStyle.SmallModulesFor(List("com.raquo.app")))
},
// Generated scala.js output will call your main() method to start your app.
scalaJSUseMainModuleInitializer := true
)
// BEGIN[codesnippets/precompile]
.settings(
precompile := {
CodeSnippetsGenerator.generate(
rootPath = java.nio.file.Path.of("."),
targetPath = java.nio.file.Path.of("client/src/main/scala/com/raquo/app/codesnippets/generated"),
packageName = "com.raquo.app.codesnippets.generated",
objectName = "GeneratedSnippets"
)
},
(Compile / compile) := ((Compile / compile) dependsOn precompile).value
)
// END[codesnippets/precompile]
.settings(
// Ignore changes to .less and .css files when watching files with sbt.
// With the suggested build configuration and usage patterns, these files are
// not included in the scala.js output, so there is no need for sbt to watch
// their contents. If sbt was also watching those files, editing them would
// cause the entire Scala.js app to do a full reload, whereas right now we
// have Vite watching those files, and it is able to hot-reload them without
// reloading the entire application – much faster and smoother.
watchSources := watchSources.value.filterNot { source =>
source.base.getName.endsWith(".less") || source.base.getName.endsWith(".css")
}
)
.dependsOn(shared.js)
// BEGIN[codesnippets/precompile]
lazy val precompile = taskKey[Unit]("runs our own pre-compile tasks")
// END[codesnippets/precompile]
val buildClient = taskKey[Unit]("Build client (frontend)")
buildClient := {
// Generate Scala.js JS output for production
(client / Compile / fullLinkJS).value
// Install JS dependencies from package-lock.json
val npmCiExitCode = Process("npm ci", cwd = (client / baseDirectory).value).!
if (npmCiExitCode > 0) {
throw new IllegalStateException(s"npm ci failed. See above for reason")
}
// Build the frontend with vite
val buildExitCode = Process("npm run build", cwd = (client / baseDirectory).value).!
if (buildExitCode > 0) {
throw new IllegalStateException(s"Building frontend failed. See above for reason")
}
// Copy vite output into server resources, where it can be accessed by the server,
// even after the server is packaged in a fat jar.
IO.copyDirectory(
source = (client / baseDirectory).value / "dist",
target = (server / baseDirectory).value / "src" / "main" / "resources" / "static"
)
}
// Always build the frontend first before packaging the application in a fat jar
(server / assembly) := (server / assembly).dependsOn(buildClient).value
val packageApplication = taskKey[File]("Package the whole application into a fat jar")
packageApplication := {
/*
To package the whole application into a fat jar, we do the following things:
- call sbt assembly to make the fat jar for us (config in the server sub-module settings)
- we move it to the ./dist folder so that the Dockerfile can be independent of Scala versions and other details
*/
val fatJar = (server / assembly).value
val target = baseDirectory.value / "dist" / "app.jar"
IO.copyFile(fatJar, target)
target
}
lazy val commonSettings = Seq(
scalacOptions ++= Seq(
"-deprecation",
//"-feature",
"-language:implicitConversions"
),
) ++ intellijTargetSettings
lazy val noPublish = Seq(
publishLocal / skip := true,
publish / skip := true
)
// -- Aliases
// Run the frontend development loop (also run vite: `cd frontend; npm run dev`)
addCommandAlias("cup", ";~client/fastLinkJS")
// Start the backend server, and make sure to stop it afterwards
addCommandAlias("sup", ";server/reStop ;~server/reStart ;server/reStop")
// Build frontend for production
addCommandAlias("cbuild", ";buildClient")
// Package the application into a jar. Run the jar with: `java -jar dist/app.jar`
addCommandAlias("jar", ";packageApplication")
// -- IntelliJ workarounds
// https://youtrack.jetbrains.com/issue/SCL-21917/Trivial-changes-to-build.sbt-cause-IDEA-to-forget-excluded-directories
// https://github.com/JetBrains/sbt-ide-settings
SettingKey[Seq[File]]("ide-excluded-directories").withRank(KeyRanks.Invisible) := Seq(
file(".idea"),
file("project/project/target"),
file("target"),
file("target-idea"),
file("client/target"),
file("client/target-idea"),
file("server/target"),
file("server/target-idea"),
file("shared/js/target"),
file("shared/js/target-idea"),
file("shared/jvm/target"),
file("shared/jvm/target-idea"),
file("shared/shared/target"),
file("dist"),
file("client/dist"),
file("client/public/assets/shoelace"),
file("client/src/main/scala/com/raquo/app/codesnippets/generated"), // Not 100% sure if it's good UX to exclude this
file("server/src/main/resources/static"),
)
// https://youtrack.jetbrains.com/issue/SCL-21839/Intellij-refactor-causes-external-incremental-sbt-compilation-to-fail-consistently
val intellijTargetSettings = {
if (System.getenv("IDEA_INITIAL_DIRECTORY") ne null)
Seq(
target := baseDirectory.value / "target-idea"
)
else Seq.empty
}