Skip to content

Commit 753171f

Browse files
committed
Index refactor
1 parent c9ff71b commit 753171f

File tree

6 files changed

+357
-106
lines changed

6 files changed

+357
-106
lines changed

project/src/live.server.scala

Lines changed: 60 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import _root_.io.circe.*
2626
import _root_.io.circe.Encoder
2727

2828
import ProxyConfig.Equilibrium
29+
import scribe.Scribe
2930

3031
sealed trait FrontendEvent derives Encoder.AsObject
3132

@@ -50,6 +51,14 @@ http:
5051
weight: 5
5152
"""
5253

54+
case class IndexHtmlConfig(
55+
indexHtmlatPath: Option[IndexHtmlDir],
56+
stylesOnly: Option[StyleDir]
57+
)
58+
59+
type StyleDir = os.Path
60+
type IndexHtmlDir = os.Path
61+
5362
object LiveServer
5463
extends CommandIOApp(
5564
name = "LiveServer",
@@ -104,14 +113,6 @@ object LiveServer
104113
)
105114
.validate("Must be a directory")(s => os.isDir(os.Path(s)))
106115

107-
val stylesDirOpt = Opts
108-
.option[String](
109-
"styles-dir",
110-
"A fully qualified path to your styles directory with LESS files in - e.g. c:/temp/helloScalaJS/styles"
111-
)
112-
.orNone
113-
.validate("Must be a directory")(sOpt => sOpt.fold(true)(s => os.isDir(os.Path(s))))
114-
115116
val portOpt = Opts
116117
.option[Int]("port", "The port you want to run the server on - e.g. 3000")
117118
.withDefault(3000)
@@ -149,35 +150,46 @@ object LiveServer
149150
)
150151
.orEmpty
151152

152-
val indexHtmlTemplateOpt: Opts[Option[String]] = Opts
153+
val stylesDirOpt: Opts[Option[StyleDir]] = Opts
154+
.option[String](
155+
"styles-dir",
156+
"A fully qualified path to your styles directory with LESS files in - e.g. c:/temp/helloScalaJS/styles"
157+
)
158+
.orNone
159+
.validate("The styles-dir must be a directory, it should have index.less at it's root")(
160+
sOpt => sOpt.fold(true)(s => os.isDir(os.Path(s)))
161+
)
162+
.map(_.map(os.Path(_)))
163+
164+
val indexHtmlTemplateOpt: Opts[Option[IndexHtmlDir]] = Opts
153165
.option[String](
154-
"path-to-index-html-template",
155-
"a path to a file which contains the index.html template you want to use."
166+
"path-to-index-html",
167+
"a path to a directory which contains index.html. The entire directory will be served as static assets"
156168
)
169+
.orNone
157170
.validate(
158-
"index.html must be a file, with a .html extension, and must contain <head> </head> and <body> </body> tags"
171+
"The path-to-index-html must be a directory. The directory must contain an index.html file. index.html must contain <head> </head> and <body> </body> tags"
159172
) {
160-
path =>
161-
os.isFile(os.Path(path)) match
162-
case false => false
163-
case true =>
164-
val f = os.Path(path)
165-
f.ext match
166-
case "html" =>
167-
val content = os.read(f)
168-
content.contains("</head>") && content.contains("</body>") && content.contains("<head>") && content
169-
.contains(
170-
"<body>"
171-
)
172-
case _ => false
173-
end match
173+
pathOpt =>
174+
pathOpt.forall {
175+
path =>
176+
os.isDir(os.Path(path)) match
177+
case false => false
178+
case true =>
179+
val f = os.Path(path) / "index.html"
180+
os.exists(f) match
181+
case false => false
182+
case true =>
183+
val content = os.read(f)
184+
content.contains("</head>") && content.contains("</body>") && content.contains("<head>") && content
185+
.contains(
186+
"<body>"
187+
)
188+
end match
189+
}
174190

175191
}
176-
.map {
177-
path =>
178-
os.read(os.Path(path))
179-
}
180-
.orNone
192+
.map(_.map(os.Path(_)))
181193

182194
val millModuleNameOpt: Opts[Option[String]] = Opts
183195
.option[String](
@@ -190,31 +202,22 @@ object LiveServer
190202
}
191203
.orNone
192204

193-
override def main: Opts[IO[ExitCode]] =
194-
given R: Random[IO] = Random.javaUtilConcurrentThreadLocalRandom[IO]
195-
def makeProxyRoutes(
196-
client: Client[IO],
197-
pathPrefix: Option[String],
198-
proxyConfig: Resource[IO, Option[Equilibrium]]
199-
): Resource[IO, HttpRoutes[IO]] =
200-
proxyConfig.flatMap {
201-
case Some(pc) =>
202-
{
203-
logger.debug("setup proxy server") >>
204-
IO(HttpProxy.servers[IO](pc, client, pathPrefix.getOrElse(???)).head._2)
205-
}.toResource
206-
207-
case None =>
208-
(
209-
logger.debug("no proxy set") >>
210-
IO(HttpRoutes.empty[IO])
211-
).toResource
212-
}
205+
val indexOpts = (indexHtmlTemplateOpt, stylesDirOpt)
206+
.mapN(IndexHtmlConfig.apply)
207+
.validate("You must provide either a styles directory or an index.html template directory, or neither") {
208+
c => c.indexHtmlatPath.isDefined || c.stylesOnly.isDefined || (c.indexHtmlatPath.isEmpty && c.stylesOnly.isEmpty)
209+
}
210+
.map(
211+
c =>
212+
c match
213+
case IndexHtmlConfig(None, None) => None
214+
case _ => Some(c)
215+
)
213216

217+
override def main: Opts[IO[ExitCode]] =
214218
(
215219
baseDirOpt,
216220
outDirOpt,
217-
stylesDirOpt,
218221
portOpt,
219222
proxyPortTargetOpt,
220223
proxyPathMatchPrefixOpt,
@@ -223,12 +226,11 @@ object LiveServer
223226
openBrowserAtOpt,
224227
extraBuildArgsOpt,
225228
millModuleNameOpt,
226-
indexHtmlTemplateOpt
229+
indexOpts
227230
).mapN {
228231
(
229232
baseDir,
230233
outDir,
231-
stylesDir,
232234
port,
233235
proxyTarget,
234236
pathPrefix,
@@ -237,7 +239,7 @@ object LiveServer
237239
openBrowserAt,
238240
extraBuildArgs,
239241
millModuleName,
240-
externalIndexHtmlTemplate
242+
indexOpts
241243
) =>
242244

243245
scribe
@@ -258,7 +260,7 @@ object LiveServer
258260
val server = for
259261
_ <- logger
260262
.debug(
261-
s"baseDir: $baseDir \n outDir: $outDir \n stylesDir: $stylesDir \n port: $port \n proxyTarget: $proxyTarget \n pathPrefix: $pathPrefix \n extraBuildArgs: $extraBuildArgs"
263+
s"baseDir: $baseDir \n outDir: $outDir \n indexOpts: $indexOpts \n port: $port \n proxyTarget: $proxyTarget \n pathPrefix: $pathPrefix \n extraBuildArgs: $extraBuildArgs"
262264
)
263265
.toResource
264266

@@ -268,7 +270,7 @@ object LiveServer
268270
linkingTopic <- Topic[IO, Unit].toResource
269271
client <- EmberClientBuilder.default[IO].build
270272

271-
proxyRoutes: HttpRoutes[IO] <- makeProxyRoutes(client, pathPrefix, proxyConfig)
273+
proxyRoutes: HttpRoutes[IO] <- makeProxyRoutes(client, pathPrefix, proxyConfig)(logger)
272274

273275
_ <- buildRunner(
274276
buildTool,
@@ -279,11 +281,7 @@ object LiveServer
279281
millModuleName
280282
)(logger)
281283

282-
indexHtmlTemplate = externalIndexHtmlTemplate.getOrElse(vanillaTemplate(stylesDir.isDefined).render)
283-
284-
app <- routes(outDir.toString(), refreshTopic, stylesDir, proxyRoutes, indexHtmlTemplate, fileToHashRef)(
285-
logger
286-
)
284+
app <- routes(outDir.toString(), refreshTopic, indexOpts, proxyRoutes, fileToHashRef)(logger)
287285

288286
_ <- seedMapOnStart(outDir, fileToHashMapRef)(logger)
289287
// _ <- stylesDir.fold(Resource.unit)(sd => seedMapOnStart(sd, mr))

project/src/proxy.routes.scala

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import org.http4s.client.Client
2+
import cats.effect.IO
3+
import cats.effect.kernel.Resource
4+
import scribe.Scribe
5+
import ProxyConfig.Equilibrium
6+
import org.http4s.HttpRoutes
7+
import cats.effect.std.Random
8+
9+
def makeProxyRoutes(
10+
client: Client[IO],
11+
pathPrefix: Option[String],
12+
proxyConfig: Resource[IO, Option[Equilibrium]]
13+
)(logger: Scribe[IO]): Resource[IO, HttpRoutes[IO]] =
14+
proxyConfig.flatMap {
15+
case Some(pc) =>
16+
{
17+
given R: Random[IO] = Random.javaUtilConcurrentThreadLocalRandom[IO]
18+
logger.debug("setup proxy server") >>
19+
IO(HttpProxy.servers[IO](pc, client, pathPrefix.getOrElse(???)).head._2)
20+
}.toResource
21+
22+
case None =>
23+
(
24+
logger.debug("no proxy set") >>
25+
IO(HttpRoutes.empty[IO])
26+
).toResource
27+
}
28+
29+
end makeProxyRoutes

project/src/routes.scala

Lines changed: 40 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import org.http4s.Request
77
import org.http4s.Response
88
import org.http4s.ServerSentEvent
99
import org.http4s.Status
10+
import org.http4s.scalatags.*
1011
import org.http4s.dsl.io.*
1112
import org.http4s.implicits.*
1213
import org.http4s.server.Router
@@ -87,48 +88,52 @@ end ETagMiddleware
8788
def routes(
8889
stringPath: String,
8990
refreshTopic: Topic[IO, Unit],
90-
stylesPath: Option[String],
91+
indexOpts: Option[IndexHtmlConfig],
9192
proxyRoutes: HttpRoutes[IO],
92-
indexHtmlTemplate: String,
9393
ref: Ref[IO, Map[String, String]]
9494
)(logger: Scribe[IO]): Resource[IO, HttpApp[IO]] =
9595

96-
val staticFiles = Router(
97-
"" -> fileService[IO](FileService.Config(stringPath))
98-
)
96+
val linkedAppWithCaching: HttpRoutes[IO] = ETagMiddleware(
97+
Router(
98+
"" -> fileService[IO](FileService.Config(stringPath))
99+
),
100+
ref
101+
)(logger)
99102

100-
val styles =
101-
stylesPath.fold(HttpRoutes.empty[IO])(
102-
path =>
103-
Router(
104-
"" -> fileService[IO](FileService.Config(path))
105-
)
106-
)
107-
108-
val makeIndex = ref.get.flatMap(mp => logger.trace(mp.toString())) >>
109-
(ref
110-
.get
111-
.map(_.toSeq.map((path, hash) => (fs2.io.file.Path(path), hash)))
112-
.map(mods => injectModulePreloads(mods, indexHtmlTemplate)))
113-
.map(html => Response[IO]().withEntity(indexHtmlTemplate).withHeaders(Header("Cache-Control", "no-cache")))
114-
115-
val overrides = HttpRoutes.of[IO] {
103+
def generatedIndexHtml(injectStyles: Boolean) = HttpRoutes.of[IO] {
116104
case GET -> Root =>
117-
logger.trace("GET /") >>
118-
makeIndex
105+
IO(Response[IO]().withEntity(vanillaTemplate(injectStyles)).withHeaders(Header("Cache-Control", "no-cache")))
119106

120107
case GET -> Root / "index.html" =>
121-
logger.trace("GET /index.html") >>
122-
makeIndex
123-
124-
case GET -> Root / "all" =>
125-
ref
126-
.get
127-
.flatTap(m => logger.trace(m.toString))
128-
.flatMap {
129-
m =>
130-
Ok(m.toString)
131-
}
108+
IO(Response[IO]().withEntity(vanillaTemplate(injectStyles)).withHeaders(Header("Cache-Control", "no-cache")))
109+
}
110+
111+
val staticAssetRoutes: HttpRoutes[IO] = indexOpts match
112+
case None => generatedIndexHtml(injectStyles = false)
113+
case Some(IndexHtmlConfig(Some(externalPath), None)) =>
114+
Router(
115+
"" -> fileService[IO](FileService.Config(externalPath.toString()))
116+
)
117+
case Some(IndexHtmlConfig(None, Some(stylesPath))) =>
118+
generatedIndexHtml(injectStyles = true).combineK(
119+
Router(
120+
"" -> fileService[IO](FileService.Config(stylesPath.toString()))
121+
)
122+
)
123+
case _ =>
124+
throw new Exception(
125+
"A seperate style path and index.html location were defined, this is not permissable"
126+
) // This should have been validated out earlier
127+
128+
val refreshRoutes = HttpRoutes.of[IO] {
129+
// case GET -> Root / "all" =>
130+
// ref
131+
// .get
132+
// .flatTap(m => logger.trace(m.toString))
133+
// .flatMap {
134+
// m =>
135+
// Ok(m.toString)
136+
// }
132137
case GET -> Root / "api" / "v1" / "sse" =>
133138
val keepAlive = fs2.Stream.fixedRate[IO](10.seconds).as(KeepAlive())
134139
Ok(
@@ -137,11 +142,7 @@ def routes(
137142
.map(msg => ServerSentEvent(Some(msg.asJson.noSpaces)))
138143
)
139144
}
140-
val app = overrides
141-
.combineK(ETagMiddleware(staticFiles, ref)(logger))
142-
.combineK(styles)
143-
.combineK(proxyRoutes)
144-
.orNotFound
145+
val app = refreshRoutes.combineK(linkedAppWithCaching).combineK(staticAssetRoutes).combineK(proxyRoutes).orNotFound
145146
IO(app).toResource
146147

147148
end routes

0 commit comments

Comments
 (0)