Skip to content

Commit d6a4524

Browse files
committed
I think that injects preloads for internal files
1 parent 7deb64d commit d6a4524

File tree

6 files changed

+126
-81
lines changed

6 files changed

+126
-81
lines changed

justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ compile:
1010
mill project.compile
1111

1212
test:
13-
mill project.test
13+
mill project.test.testOnly SafariSuite && mill project.test.testOnly RoutesSuite && mill project.test.testOnly UtilityFcs
1414

1515
checkOpts:
1616
mill project.run --help

project/src/htmlGen.scala

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import scalatags.Text.all.*
22

33
import fs2.io.file.Path
4+
import cats.effect.kernel.Ref
5+
import cats.effect.IO
46

57
def lessStyle(withStyles: Boolean): Seq[Modifier] =
68
if withStyles then
@@ -49,22 +51,17 @@ def injectRefreshScript(template: String) =
4951

5052
end injectRefreshScript
5153

52-
def injectModulePreloads(modules: Seq[(Path, String)], template: String) =
53-
val modulesStrings =
54-
for
55-
m <- modules
56-
if m._1.toString.endsWith(".js")
57-
yield link(rel := "modulepreload", href := s"${m._1}?hash=${m._2}").render
54+
def injectModulePreloads(ref: Ref[IO, Map[String, String]], template: String) =
55+
val preloads = makeInternalPreloads(ref)
56+
preloads.map: modules =>
57+
val modulesStringsInject = modules.mkString("\n", "\n", "\n")
58+
val headCloseTag = "</head>"
59+
val insertionPoint = template.indexOf(headCloseTag)
5860

59-
val modulesStringsInject = modulesStrings.mkString("\n", "\n", "\n")
60-
val headCloseTag = "</head>"
61-
val insertionPoint = template.indexOf(headCloseTag)
62-
63-
val newHtmlContent = template.substring(0, insertionPoint) +
64-
modulesStringsInject +
65-
template.substring(insertionPoint)
66-
67-
newHtmlContent
61+
val newHtmlContent = template.substring(0, insertionPoint) +
62+
modulesStringsInject +
63+
template.substring(insertionPoint)
64+
newHtmlContent
6865

6966
end injectModulePreloads
7067

@@ -93,17 +90,35 @@ def makeHeader(modules: Seq[(Path, String)], withStyles: Boolean) =
9390
)
9491
end makeHeader
9592

96-
def vanillaTemplate(withStyles: Boolean) = html(
97-
head(
98-
meta(
99-
httpEquiv := "Cache-control",
100-
content := "no-cache, no-store, must-revalidate"
93+
def makeInternalPreloads(ref: Ref[IO, Map[String, String]]) =
94+
val keys = ref.get.map(_.toSeq)
95+
keys.map {
96+
modules =>
97+
for
98+
m <- modules
99+
if m._1.toString.endsWith(".js") && m._1.toString.startsWith("internal")
100+
yield link(rel := "modulepreload", src := s"${m._1}")
101+
end for
102+
}
103+
104+
end makeInternalPreloads
105+
106+
def vanillaTemplate(withStyles: Boolean, ref: Ref[IO, Map[String, String]]) =
107+
val preloads = makeInternalPreloads(ref)
108+
preloads.map: modules =>
109+
html(
110+
head(
111+
meta(
112+
httpEquiv := "Cache-control",
113+
content := "no-cache, no-store, must-revalidate",
114+
modules
115+
)
116+
),
117+
body(
118+
lessStyle(withStyles),
119+
script(src := "/main.js", `type` := "module"),
120+
div(id := "app"),
121+
refreshScript
122+
)
101123
)
102-
),
103-
body(
104-
lessStyle(withStyles),
105-
script(src := "/main.js", `type` := "module"),
106-
div(id := "app"),
107-
refreshScript
108-
)
109-
)
124+
end vanillaTemplate

project/src/middleware/ETagMiddleware.scala

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,25 @@ object ETagMiddleware:
2727
map.get(req.uri.path.toString.drop(1)) match
2828
case Some(hash) =>
2929
logger.debug(req.uri.toString) >>
30-
IO(resp.putHeaders(Header.Raw(ci"ETag", hash)))
30+
IO(
31+
resp.putHeaders(
32+
Header.Raw(ci"ETag", hash),
33+
Header.Raw(ci"Cache-control", "Must-Revalidate"),
34+
Header.Raw(ci"Cache-control", "No-cache"),
35+
Header.Raw(ci"Cache-control", "max-age=0"),
36+
Header.Raw(ci"Cache-control", "public")
37+
)
38+
)
3139
case None =>
3240
logger.debug(req.uri.toString) >>
33-
IO(resp)
41+
IO(
42+
resp.putHeaders(
43+
Header.Raw(ci"Cache-control", "Must-Revalidate"),
44+
Header.Raw(ci"Cache-control", "No-cache"),
45+
Header.Raw(ci"Cache-control", "max-age=0"),
46+
Header.Raw(ci"Cache-control", "public")
47+
)
48+
)
3449
end match
3550
}
3651
end respondWithEtag

project/src/routes.scala

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import org.http4s.server.middleware.Logger
1919
import org.http4s.server.staticcontent.*
2020
import org.http4s.server.staticcontent.FileService
2121
import org.typelevel.ci.CIStringSyntax
22+
import org.http4s.EntityBody
2223

2324
import fs2.*
2425
import fs2.concurrent.Topic
@@ -70,8 +71,8 @@ def routes[F[_]: Files: MonadThrow](
7071
ref
7172
)(logger)
7273

73-
val hashFalse = vanillaTemplate(false).render.hashCode.toString
74-
val hashTrue = vanillaTemplate(true).render.hashCode.toString
74+
// val hashFalse = vanillaTemplate(false).render.hashCode.toString
75+
// val hashTrue = vanillaTemplate(true).render.hashCode.toString
7576
val zdt = ZonedDateTime.now()
7677

7778
def userBrowserCacheHeaders(resp: Response[IO], lastModZdt: ZonedDateTime, injectStyles: Boolean) =
@@ -97,56 +98,49 @@ def routes[F[_]: Files: MonadThrow](
9798
object StaticHtmlMiddleware:
9899
def apply(service: HttpRoutes[IO], injectStyles: Boolean)(logger: Scribe[IO]): HttpRoutes[IO] = Kleisli {
99100
(req: Request[IO]) =>
100-
req.headers.get(ci"If-None-Match").map(_.toList) match
101-
case Some(h :: Nil) if h.value == hashFalse => OptionT.liftF(IO(Response[IO](Status.NotModified)))
102-
case Some(h :: Nil) if h.value == hashTrue => OptionT.liftF(IO(Response[IO](Status.NotModified)))
103-
case _ => service(req).semiflatMap(userBrowserCacheHeaders(_, zdt, injectStyles))
104-
end match
105-
101+
service(req).semiflatMap(userBrowserCacheHeaders(_, zdt, injectStyles))
106102
}
107103

108104
end StaticHtmlMiddleware
109105

110-
def generatedIndexHtml(injectStyles: Boolean) =
106+
def generatedIndexHtml(injectStyles: Boolean, modules: Ref[IO, Map[String, String]]) =
111107
StaticHtmlMiddleware(
112108
HttpRoutes.of[IO] {
113109
case req @ GET -> Root =>
114110
logger.trace("Generated index.html") >>
115-
IO(
111+
vanillaTemplate(injectStyles, modules).map: html =>
116112
Response[IO]()
117-
.withEntity(vanillaTemplate(injectStyles))
113+
.withEntity(html)
118114
.withHeaders(
119115
Header.Raw(ci"Cache-Control", "no-cache"),
120116
Header.Raw(
121117
ci"ETag",
122-
injectStyles match
123-
case true => hashTrue
124-
case false => hashFalse
118+
html.hashCode.toString
125119
),
126120
Header.Raw(ci"Last-Modified", formatter.format(zdt)),
127121
Header.Raw(
128122
ci"Expires",
129123
httpCacheFormat(ZonedDateTime.ofInstant(Instant.now().plusSeconds(10000000), ZoneId.of("GMT")))
130124
)
131125
)
132-
)
126+
133127
},
134128
injectStyles
135129
)(logger).combineK(
136130
StaticHtmlMiddleware(
137131
HttpRoutes.of[IO] {
138132
case GET -> Root / "index.html" =>
139-
IO {
140-
Response[IO]().withEntity(vanillaTemplate(injectStyles))
141-
}
133+
vanillaTemplate(injectStyles, modules).map: html =>
134+
Response[IO]().withEntity(html)
135+
142136
},
143137
injectStyles
144138
)(logger)
145139
)
146140

147141
// val formatter = DateTimeFormatter.RFC_1123_DATE_TIME
148-
val staticAssetRoutes: HttpRoutes[IO] = indexOpts match
149-
case None => generatedIndexHtml(injectStyles = false)
142+
def staticAssetRoutes(modules: Ref[IO, Map[String, String]]): HttpRoutes[IO] = indexOpts match
143+
case None => generatedIndexHtml(injectStyles = false, modules)
150144

151145
case Some(IndexHtmlConfig.IndexHtmlPath(path)) =>
152146
StaticMiddleware(
@@ -161,9 +155,9 @@ def routes[F[_]: Files: MonadThrow](
161155
Router(
162156
"" -> fileService[IO](FileService.Config(stylesPath.toString()))
163157
)
164-
)(logger).combineK(generatedIndexHtml(injectStyles = true))
158+
)(logger).combineK(generatedIndexHtml(injectStyles = true, modules))
165159

166-
val clientSpaRoutes: HttpRoutes[IO] =
160+
def clientSpaRoutes(modules: Ref[IO, Map[String, String]]): HttpRoutes[IO] =
167161
clientRoutingPrefix match
168162
case None => HttpRoutes.empty[IO]
169163
case Some(spaRoute) =>
@@ -173,9 +167,9 @@ def routes[F[_]: Files: MonadThrow](
173167
StaticHtmlMiddleware(
174168
HttpRoutes.of[IO] {
175169
case req @ GET -> root /: path =>
176-
IO(
177-
Response[IO]().withEntity(vanillaTemplate(false))
178-
)
170+
vanillaTemplate(false, modules).map: html =>
171+
Response[IO]().withEntity(html)
172+
179173
},
180174
false
181175
)(logger)
@@ -184,9 +178,8 @@ def routes[F[_]: Files: MonadThrow](
184178
StaticHtmlMiddleware(
185179
HttpRoutes.of[IO] {
186180
case GET -> root /: spaRoute /: path =>
187-
IO(
188-
Response[IO]().withEntity(vanillaTemplate(true))
189-
)
181+
vanillaTemplate(true, modules).map: html =>
182+
Response[IO]().withEntity(html)
190183
},
191184
true
192185
)(logger)
@@ -195,7 +188,24 @@ def routes[F[_]: Files: MonadThrow](
195188
StaticFileMiddleware(
196189
HttpRoutes.of[IO] {
197190
case req @ GET -> spaRoute /: path =>
198-
StaticFile.fromPath(dir / "index.html", Some(req)).getOrElseF(NotFound())
191+
StaticFile
192+
.fromPath(dir / "index.html", Some(req))
193+
.getOrElseF(NotFound())
194+
.flatMap {
195+
f =>
196+
f.body
197+
.through(text.utf8.decode)
198+
.compile
199+
.string
200+
.flatMap: body =>
201+
for str <- injectModulePreloads(modules, body)
202+
yield
203+
val bytes = str.getBytes()
204+
f.withEntity(bytes)
205+
f
206+
207+
}
208+
199209
},
200210
dir / "index.html"
201211
)(logger)
@@ -215,8 +225,8 @@ def routes[F[_]: Files: MonadThrow](
215225
refreshRoutes
216226
.combineK(linkedAppWithCaching)
217227
.combineK(proxyRoutes)
218-
.combineK(clientSpaRoutes)
219-
.combineK(staticAssetRoutes)
228+
.combineK(clientSpaRoutes(ref))
229+
.combineK(staticAssetRoutes(ref))
220230
)
221231

222232
clientRoutingPrefix.fold(IO.unit)(s => logger.trace(s"client spa at : $s")).toResource >>

project/test/src/UtilityFcts.scala

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
class UtilityFcs extends munit.FunSuite:
1+
import munit.CatsEffectSuite
2+
import cats.effect.kernel.Ref
3+
import cats.effect.IO
4+
5+
class UtilityFcs extends CatsEffectSuite:
26

37
test("That we actually inject the preloads ") {
48

@@ -14,20 +18,17 @@ class UtilityFcs extends munit.FunSuite:
1418

1519
}
1620

17-
test(" That we can inject preloads into a template") {
18-
val html = injectModulePreloads(
19-
modules = Seq(
20-
(fs2.io.file.Path("main.js"), "hash")
21-
),
22-
template = "<html><head></head></html>"
23-
)
24-
assert(html.contains("hash"))
25-
assertEquals(
26-
html,
27-
"""<html><head>
28-
<link rel="modulepreload" href="main.js?hash=hash" />
29-
</head></html>"""
30-
)
21+
ResourceFunFixture {
22+
for
23+
ref <- Ref.of[IO, Map[String, String]](Map.empty).toResource
24+
_ <- ref.update(_.updated("internal.js", "hash")).toResource
25+
yield ref
26+
}.test("That we can make internal preloads") {
27+
ref =>
28+
val html = injectModulePreloads(ref, "<html><head></head><body></body></html>")
29+
html.map: html =>
30+
assert(html.contains("modulepreload"))
31+
assert(html.contains("internal.js"))
3132
}
3233

3334
test(" That we can inject a refresh script") {

project/test/src/liveServer.test.scala

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import cats.effect.IO
1717

1818
import LiveServer.LiveServerConfig
1919
import munit.CatsEffectSuite
20+
import cats.effect.kernel.Ref
2021

2122
/*
2223
Run
@@ -68,12 +69,14 @@ trait PlaywrightTest extends CatsEffectSuite:
6869
var page: Page = uninitialized
6970

7071
val options = new BrowserType.LaunchOptions()
71-
// options.setHeadless(false);
72+
options.setHeadless(false);
7273

7374
// def testDir(base: os.Path) = os.pwd / "testDir"
7475
def outDir(base: os.Path) = base / ".out"
7576
def styleDir(base: os.Path) = base / "styles"
7677

78+
val vanilla = vanillaTemplate(true, Ref.unsafe(Map[String, String]())).unsafeRunSync()
79+
7780
val files =
7881
IO {
7982
val tempDir = os.temp.dir()
@@ -93,7 +96,8 @@ trait PlaywrightTest extends CatsEffectSuite:
9396
os.makeDir(staticDir)
9497
os.write.over(tempDir / "hello.scala", helloWorldCode("Hello"))
9598
os.write.over(staticDir / "index.less", "h1{color:red}")
96-
os.write.over(staticDir / "index.html", vanillaTemplate(true).render)
99+
println(vanilla.render)
100+
os.write.over(staticDir / "index.html", vanilla.render)
97101
(tempDir, staticDir)
98102
}.flatTap {
99103
tempDir =>
@@ -245,11 +249,11 @@ trait PlaywrightTest extends CatsEffectSuite:
245249
) >>
246250
assertIO(
247251
client.expect[String](s"http://localhost:$port"),
248-
vanillaTemplate(true).render
252+
vanilla.render
249253
) >>
250254
assertIO(
251255
client.expect[String](s"http://localhost:$port/app/spaRoute"),
252-
vanillaTemplate(true).render
256+
vanilla.render
253257
)
254258

255259
}

0 commit comments

Comments
 (0)