Skip to content

Commit

Permalink
fix perfomance regression in preview server bootstrapping (#524)
Browse files Browse the repository at this point in the history
  • Loading branch information
jenshalm committed Sep 15, 2023
1 parent 0aa4f97 commit 48fc850
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 26 deletions.
11 changes: 7 additions & 4 deletions preview/src/main/scala/laika/preview/ASTPageTransformer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import laika.ast.{
CodeBlock,
Document,
DocumentTreeRoot,
Path,
RewritePhase,
RootElement,
Section,
Expand All @@ -47,14 +48,16 @@ private[preview] object ASTPageTransformer {
val description: String = "AST URL extension for preview server"
private val outputName = "ast"

def translateASTPath(path: Path): Path = {
val base = path.withoutFragment / outputName
path.fragment.fold(base)(base.withFragment)
}

override def extendPathTranslator
: PartialFunction[ExtensionBundle.PathTranslatorExtensionContext, PathTranslator] = {
case context =>
PathTranslator.postTranslate(context.baseTranslator) { path =>
if (path.suffix.contains("html")) {
val base = path.withoutFragment / outputName
path.fragment.fold(base)(base.withFragment)
}
if (path.suffix.contains("html")) translateASTPath(path)
else path
}
}
Expand Down
47 changes: 29 additions & 18 deletions preview/src/main/scala/laika/preview/RouteBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,21 @@
package laika.preview

import java.io.InputStream

import cats.syntax.all._
import cats.effect.{ Async, Resource, Sync }
import fs2.concurrent.Topic
import fs2.io.readInputStream
import laika.preview.ServerBuilder.Logger
import org.http4s.dsl.Http4sDsl
import org.http4s.{ CacheDirective, EntityEncoder, Headers, HttpRoutes, MediaType, ServerSentEvent }
import org.http4s.{
CacheDirective,
EntityEncoder,
Headers,
HttpRoutes,
MediaType,
Response,
ServerSentEvent
}
import org.http4s.headers.{ `Cache-Control`, `Content-Type` }

import scala.concurrent.duration.DurationInt
Expand All @@ -48,6 +55,25 @@ private[preview] class RouteBuilder[F[_]: Async](

private val noCache = `Cache-Control`(CacheDirective.`no-store`)

def serve(laikaPath: laika.ast.Path, result: Option[SiteResult[F]]): F[Response[F]] =
result match {
case Some(RenderedResult(content)) =>
logger(s"serving path $laikaPath - transformed markup") *>
Ok(content).map(
_
.withHeaders(noCache)
.withContentType(`Content-Type`(MediaType.text.html))
)
case Some(StaticResult(input)) =>
logger(s"serving path $laikaPath - static input") *> {
val mediaType = laikaPath.suffix.flatMap(mediaTypeFor).map(`Content-Type`(_))
Ok(input).map(_.withHeaders(Headers(mediaType, noCache)))
}
case Some(LazyResult(res)) => res.flatMap(serve(laikaPath, _))
case None =>
logger(s"serving path $laikaPath - not found") *> NotFound()
}

def build: HttpRoutes[F] = HttpRoutes.of[F] {

case GET -> Root / "laika" / "events" =>
Expand All @@ -56,22 +82,7 @@ private[preview] class RouteBuilder[F[_]: Async](

case GET -> path =>
val laikaPath = laika.ast.Path.parse(path.toString)
cache.get.map(_.get(laikaPath.withoutFragment)).flatMap {
case Some(RenderedResult(content)) =>
logger(s"serving path $laikaPath - transformed markup") *>
Ok(content).map(
_
.withHeaders(noCache)
.withContentType(`Content-Type`(MediaType.text.html))
)
case Some(StaticResult(input)) =>
logger(s"serving path $laikaPath - static input") *> {
val mediaType = laikaPath.suffix.flatMap(mediaTypeFor).map(`Content-Type`(_))
Ok(input).map(_.withHeaders(Headers(mediaType, noCache)))
}
case None =>
logger(s"serving path $laikaPath - not found") *> NotFound()
}
cache.get.map(_.get(laikaPath.withoutFragment)).flatMap(serve(laikaPath, _))
}

}
31 changes: 27 additions & 4 deletions preview/src/main/scala/laika/preview/SiteTransformer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package laika.preview

import cats.effect.{ Async, Resource }
import cats.syntax.all._
import cats.effect.syntax.all._
import fs2.Chunk
import laika.api.Renderer
import laika.api.builder.OperationConfig
Expand Down Expand Up @@ -98,13 +99,32 @@ private[preview] class SiteTransformer[F[_]: Async](
}
}

private def transformASTLazily(tree: ParsedTree[F], html: ResultMap[F]): F[ResultMap[F]] = {

val transformer = for {
modifiedRoot <- Async[F].delay(
tree.modifyRoot(ASTPageTransformer.transform(_, parser.config))
)
resultMap <- transformHTML(modifiedRoot, astRenderer)
} yield resultMap

def buildLazyMap(delegate: F[ResultMap[F]]): ResultMap[F] = {
html.keySet
.map(ASTPageTransformer.ASTPathTranslator.translateASTPath)
.map { astPath =>
val result = LazyResult(delegate.map(_.get(astPath)))
(astPath, result: SiteResult[F])
}
.toMap
}

transformer.memoize.map(buildLazyMap)
}

val transform: F[SiteResults[F]] = for {
tree <- parse
html <- transformHTML(tree, htmlRenderer)
ast <- transformHTML(
tree.modifyRoot(ASTPageTransformer.transform(_, parser.config)),
astRenderer
)
ast <- transformASTLazily(tree, html)
ebooks <- Async[F].fromEither(transformBinaries(tree).leftMap(ConfigException.apply))
} yield {
new SiteResults(staticFiles ++ ast ++ html ++ ebooks)
Expand Down Expand Up @@ -201,3 +221,6 @@ private[preview] case class RenderedResult[F[_]: Async](content: String) extends

private[preview] case class StaticResult[F[_]: Async](content: fs2.Stream[F, Byte])
extends SiteResult[F]

private[preview] case class LazyResult[F[_]: Async](result: F[Option[SiteResult[F]]])
extends SiteResult[F]

0 comments on commit 48fc850

Please sign in to comment.