markdown

Embedding the parser

Drop the library into your Scala app — JVM, JS, or Native — and render markdown content with the right config for your audience.

The library has zero runtime third-party dependencies, and the same line in build.sbt works on every platform. Embedding it in an existing app is a one-import change.

1. Add the dependency

libraryDependencies += "io.github.edadma" %%% "markdown" % "0.4.3"

The %%% form picks the right artifact for whichever Scala platform you’re on.

2. Pick a config that fits your audience

Three common shapes:

import io.github.edadma.markdown.*

// User comments, support tickets — predictable, no surprises.
val commentCfg = MarkdownConfig.default

// Knowledge base / wiki — table-heavy, link-heavy, anchor-heavy.
val wikiCfg = MarkdownConfig.default.copy(
  tables            = true,
  extendedAutolinks = true,
  autoHeadingIds    = true,
  smartPunctuation  = true,
)

// Author-facing docs / notes — full Obsidian / Hugo flavour.
val docsCfg = MarkdownConfig.all

There’s no global config. Pass the one you want at every call site, or store one per-context.

3. Render

class CommentService:
  def renderComment(body: String): String =
    renderToHTML(body, commentCfg)

That’s the full integration for the simple case.

4. Cache the parsed AST when it pays

For server-side rendering of frequently re-rendered content (a comment loaded a thousand times, a doc page hit on every request), cache the Document rather than the rendered HTML — it’s smaller and the rendering step is cheap.

import io.github.edadma.markdown.*

class CachedRenderer(cfg: MarkdownConfig):
  private val cache = scala.collection.mutable.HashMap.empty[String, Document]

  def render(key: String, body: => String): String =
    val doc = cache.getOrElseUpdate(key, parseDocumentContent(body, cfg))
    renderToHTML(doc, cfg)

Invalidation strategy is your problem; the library is referentially transparent over (input, config).

Cross-document analysis (a wiki where [Page] resolves against another file’s link reference table) needs the references separately:

val (doc, refs) = parseDocumentContentWithRefs(body, cfg)

refs: Map[String, LinkReference] carries every [label]: dest "title" that appeared in the source. Hold them in your store; merge across files at render time as you see fit.

6. Sanitization

The library does not sanitize. RawHTML and HTMLBlock pass through verbatim — that’s the CommonMark contract. If you’re rendering untrusted input, run the output through a sanitizer (jsoup’s Cleaner, OWASP HTML Sanitizer, DOMPurify on the JS side) after rendering.

You could also walk the AST and remove raw-HTML nodes before rendering — see Custom renderer for the walker pattern.

7. Cross-platform notes

The same code runs on JVM, Scala.js (Node 20+), and Scala Native. The only platform-specific concern is filesystem I/O (cross_platform.readFile / writeFile), which is exposed by a sister library if you need it.

For the JS side specifically, the published @edadma/markdown npm bundle gives you the same API as direct Scala-from-Scala.js consumption — pick whichever surface fits your stack.