Laika Documentation

repository·main·Indexed 19 days ago

https://github.com/typelevel/laika

A purely functional site and e-book generator for Scala developers. Laika transforms Markdown and reStructuredText into HTML, EPUB, and PDF, supporting JVM, sbt, and Scala.js runtimes. It features a customizable text markup transformer, an integrated preview server, and a highly extensible AST-based directive system for custom tags. Compatible with Scala 3, 2.13, and 2.12, and integrates with cats-effect 3.x.

Tokens
55.2K
Snippets
149
Records
238
Agent score
66%

What's inside Laika

  1. Overview of Laika

    main

    Laika is a site and e-book generator and a customizable text markup transformer. It is designed to work with sbt, Scala, and Scala.js. It supports converting Markdown and reStructuredText into various output formats including HTML, EPUB, and PDF.

    Key features include:

    • Flexible Runtime: Use it as an sbt plugin, a JVM Scala library, or in the browser via Scala.js.
    • Purely Functional: Fully referentially transparent, uses no exceptions or runtime reflection, and integrates with cats-effect for polymorphic effect handling.
    • Rich Feature Set: Includes an integrated preview server, syntax highlighting, link validation, auto-generated navigation, and versioned documentation.
    • Lightweight Theme: The default 'Helium' theme uses minimal handcrafted CSS and JS without external frameworks like Bootstrap.
    • Highly Extensible: Allows processing the document AST, adjusting rendering for individual nodes, or extending markup languages with custom directives.
  2. Understand Laika's core design principles

    main

    Laika is a documentation toolkit designed to be decoupled from specific tools, runtimes, and formats. Key principles include:

    • Standalone & Tool-Agnostic: Does not require external tools like Jekyll or Hugo. It can be embedded in server-side or browser applications.
    • Multi-Runtime Support: The laika-core module is compatible with both the JVM and Scala.js (browser), making it suitable for client-side processing. It avoids runtime reflection.
    • Build Tool Independence: While an sbt plugin is available, the core functionality is a library that can be used with Mill or integrated directly into application code.
    • Format Agnostic: Decouples input markup (Markdown, reStructuredText) from output formats (HTML, EPUB, PDF) using a generic Document AST.
    • Virtual File System: Uses a Virtual Tree Abstraction instead of being tied to the physical file system, allowing for in-memory document generation and recursive merging of input directories.
    • Purely Functional: The API is referentially transparent and uses polymorphic effect types based on cats-effect.
  3. Supported platforms for Laika

    main

    Laika can be integrated into various environments depending on your needs:

    • sbt Plugin: For version 1.x, ideal for build and CI pipelines (e.g., generating Scala project documentation).
    • JVM Library: Supports Scala 3.3+, 2.13, or 2.12.
    • Scala.js Library: Supports Scala.js 1.13+ applications. Note that in Scala.js, File/Stream IO, EPUB, and PDF output are not supported.
  4. Supported input and output formats

    main

    Laika supports a variety of markup languages and output formats:

    Input Formats:

    • Markdown (including GitHub Flavor)
    • reStructuredText (including standard directives)
    • HOCON (used for configuration and directive attributes)

    Output Formats:

    • HTML sites (based on templates)
    • E-Books (EPUB & PDF) with auto-generated navigation

    Laika also includes a lightweight default theme with configurable styling for sites, EPUB, and PDF output.

  5. Understand changes to configuration classes

    main

    Many configuration types have been converted from case class to regular class (or trait) to allow for easier evolution.

    Key changes to configuration patterns:

    • Required Parameters: The apply methods now only accept required parameters.
    • Optional Properties: Use the withXX pattern to set optional properties (e.g., myConfig.withProperty(value)).
    • Defaults: For types with no required properties, use XX.defaults (if pre-populated by the library) or XX.empty (if not pre-populated).
  6. Use the Theme API for styling and templates

    main

    The Theme API expands on ExtensionBundle by allowing you to pre-populate the input tree with templates and styles. This enables providing ready-to-use rendering styles (CSS, JavaScript, and templates) without requiring users to craft their own.

    Laika includes a default lightweight theme called Helium, which provides default styles for web sites, EPUB, and PDF. You can often achieve desired results by simply tweaking Helium's settings.

  7. Override specific AST node rendering

    main

    You can override the output of a renderer for specific types of AST nodes while keeping the default behavior for others. This is useful when customization is coupled to a specific output format (e.g., adding a CSS class to an HTML tag).

    For generic logic that should apply to all output formats, use [AST Rewriting] instead.

    A custom renderer is defined as a PartialFunction[(Formatter, Element), String]. If the partial function does not match a specific node type, Laika falls back to the default renderer. If multiple custom renderers are provided, they are tried in the order they were added.

    import laika.ast._
    import laika.api.format.TagFormatter
    
    // Example: Adding a 'big' class to emphasized text in HTML
    val renderer: PartialFunction[(TagFormatter, Element), String] = {
      case (fmt, e: Emphasized) => 
        fmt.element("em", e, "class" -> "big") 
    }
  8. How parser extensions work in Laika

    main

    Laika allows extending text markup syntax via two primary methods:

    1. Implementing Directives: Best for convenience when using a common syntax for declaring attributes and bodies. This does not require writing custom parser combinators.
    2. Writing Parser Extensions: Best when you have highly specialized syntax requirements (e.g., a custom table format) or want the most concise syntax for frequent elements (e.g., #123 instead of @:ticket(123)).

    Markup parsing in Laika is a multi-pass operation. It first identifies blocks (like blockquotes or lists) and then parses the text within those blocks for inline spans (like links or emphasis). Extensions can be implemented for either Spans (inline) or Blocks (structural).

  9. Use Laika's Virtual Tree for flexible document composition

    main

    Instead of relying solely on the physical file system, Laika uses a Virtual Tree Abstraction to organize markup documents, templates, and configuration files. This provides several advantages:

    • Recursive Merging: You can specify multiple input directories. Laika will perform a recursive merge of these trees, allowing you to keep reusable templates, styles, and static files in separate locations from your primary markup files.
    • In-Memory Content: You can generate content in-memory and assign it a virtual path. This allows you to 'mount' generated content at a specific location without writing it to the disk first, which is highly efficient for CI environments.
    • Virtual Linking: Internal links (e.g., ../intro.md) are resolved based on virtual paths, meaning they can seamlessly reference both physical files and in-memory generated content.
  10. How Laika templates work

    main

    Laika uses a lightweight template engine to customize the output of transformed markup documents. Templates allow you to specify where content from markup documents is inserted and add dynamic elements like navigation bars.

    Key distinctions:

    • Templates vs. Renderers: Templates control the structure and placement of content (e.g., where the body goes in an HTML shell). They do not control how individual AST nodes (like a bold tag or a link) are rendered. For that, use [Overriding Renderers].
    • Templates vs. Themes: When using a theme (like Helium), it is often better to use the theme's high-level configuration options. Use custom templates only when subtle tweaks to a theme are insufficient or when working without a theme.
  11. Separate Parsing and Rendering

    main

    If you need to render the same input to multiple formats (e.g., HTML, EPUB, and PDF), do not create multiple Transformer instances. Instead, create one Parser and multiple Renderer instances. This allows you to parse the source once and reuse the resulting DocumentTreeRoot.

    // 1. Create a single parser
    val parser = MarkupParser.of(Markdown).using(Markdown.GitHubFlavor).parallel[IO].build
    val config = parser.config
    
    // 2. Create multiple renderers using the same config
    val htmlRenderer = Renderer.of(HTML).withConfig(config).parallel[IO].build
    val epubRenderer = Renderer.of(EPUB).withConfig(config).parallel[IO].build
    
    // 3. Wire them together
    parser.use { p =>
      htmlRenderer.use { h =>
        epubRenderer.use { e =>
          p.fromDirectory("src").parse.flatMap { tree =>
            val htmlOp = h.from(tree.root).toDirectory("target").render
            val epubOp = e.from(tree.root).toFile("out.epub").render
            (htmlOp, epubOp).parMapN { (_, _) => () }
          }
        }
      }
    }
  12. How Laika transformations work

    main

    Laika processes documents through four distinct transformation phases. Understanding these phases is essential for knowing where to hook in your extensions:

    1. The parsing step: Text markup and templates are parsed into a generic internal AST (Abstract Syntax Tree). This AST is format-agnostic.
    2. The AST transformation: The initial AST is refined. Because parsers run locally (in parallel), nodes requiring global context (like internal references or auto-numbered footnotes) use a DocumentCursor to access other parts of the tree.
    3. Applying templates to markup documents: The markup AST is inserted into the template AST at designated insertion points. This is essentially a second AST transformation.
    4. Rendering: The final combined AST is rendered into specific output formats (e.g., HTML, PDF, EPUB). This is the only phase specific to a particular output format.