Upgrade to Pro — share decks privately, control downloads, hide ads and more …

New Dokka - Designed for Fearless Creativity

New Dokka - Designed for Fearless Creativity

Pluggability is hard. Many software projects avoid it by all means possible. Many try to contain it in the smallest possible scope. We believe that developers are creative and have specific requirements for their documentation engine. Some of them want to be able to render their docs in javadoc-compatible format. Some of them want features like a full-text search or runnable code snippets inside of the docs. And there are some that need features that we’ve never even thought about. To satisfy all of them, we decided to embrace full pluggability. And we decided to make pluggability fearless, so anyone can mix and match existing plugins or easily create new ones from scratch, without the fear of breaking anything.

In my talk, I would like to tell you how we decided to redesign and reimplement Dokka from scratch. I will introduce you to all the dangers and risks of pluggability and tell you how we mitigated them by design, grounded in strong guarantees. Finally, I want to show how easily and how much you can do with your old, boring documentation using new Dokka.

Paweł Marks

June 05, 2020
Tweet

Other Decks in Programming

Transcript

  1. Dokka - more features? •HTML •Markdown •Javadoc •Parsing Java code

    •Synthetic symbols •Code samples •Documentation for REST •Advanced search •Platform markers •Integration with github
  2. Solved with formatters •HTML •Markdown •Javadoc •Parsing Java code •Synthetic

    symbols •Code samples •Documentation for REST •Advanced search •Platform markers •Integration with github
  3. Hardcoded solutions •HTML •Markdown •Javadoc •Parsing Java code •Synthetic symbols

    •Code samples •Documentation for REST •Advanced search •Platform markers •Integration with github
  4. Solved with binary patching •HTML •Markdown •Javadoc •Parsing Java code

    •Synthetic symbols •Code samples •Documentation for REST •Advanced search •Platform markers •Integration with github
  5. Why it is not maintainable •Inability to cooperate between “plugins”

    •Model becoming more and more dissolved •Lack of guarantees
  6. The problem /** * A sample extension function * When

    $a \ne 0$, there are two solutions to \(ax^2 + bx + c = 0\) * and they are $$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$ * @usesMathJax */ fun Clock.extensionFun() { }
  7. The solution plugins { kotlin("jvm") version "1.3.72" } group "org.example"

    version "1.0-SNAPSHOT" repositories { mavenCentral() mavenLocal() } dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") compileOnly("org.jetbrains.dokka:dokka-core:0.11.0-SNAPSHOT") }
  8. The models Input Documentables Pages Output { package } {

    class } { function } { method } { property }
  9. The models Input Documentables Pages Output class index header(“class Abc”)

    block(“methods”) { group { text(“fun add”) signature(…) text(“some docs”) … } … }
  10. Extensions Input Documentables Pages Output forJava forKotlin syntheticFunctionsGenerator defaultTranslator functionMerger

    navigationGenerator HTMLRenderer documentableTransformer: documentableToPageTranslator: renderer: sourceToDocumentableTranslator: pageTransformer:
  11. Our extension class MathjaxPlugin: DokkaPlugin() { val mathjaxTransformer by extending

    { CoreExtensions.pageTransformer with MathjaxTransformer } } object MathjaxTransformer
  12. Our extension object MathjaxTransformer : PageTransformer { override fun invoke(input:

    RootPageNode): RootPageNode { TODO("Not yet implemented") } } interface ContentPage: PageNode { val content: ContentNode val dri: Set<DRI> val documentable: Documentable? val embeddedResources: List<String> … }
  13. Our extension private const val ANNOTATION = "usesMathJax" private const

    val LIB_PATH = “https://cdnjs.cloudflare.com/ajax/libs/mathjax/… object MathjaxTransformer : PageTransformer { override fun invoke(input: RootPageNode) = input.transformContentPagesTree { it.modified( embeddedResources = it.embeddedResources + if (it.isNeedingMathjax) listOf(LIB_PATH) else emptyList() ) } }
  14. Our extension private const val ANNOTATION = "usesMathJax" private const

    val LIB_PATH = “https://cdnjs.cloudflare.com/ajax/libs/mathjax/… object MathjaxTransformer : PageTransformer { override fun invoke(input: RootPageNode) = … private val ContentPage.isNeedingMathjax get() = documentable?.documentation?.values ?.flatMap { it.children } .orEmpty() .any { (it as? CustomWrapperTag)?.name == ANNOTATION } }
  15. package org.example import . . . class MathjaxPlugin: DokkaPlugin() {

    val mathjaxTransformer by extending { CoreExtensions.pageTransformer with MathjaxTransformer } } private const val ANNOTATION = "usesMathJax" private const val LIB_PATH = "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.6/MathJax.js?config=TeX-AMS_SVG&latest" object MathjaxTransformer : PageTransformer { override fun invoke(input: RootPageNode) = input.transformContentPagesTree { it.modified( embeddedResources = it.embeddedResources + if (it.isNeedingMathjax) listOf(LIB_PATH) else emptyList() ) } private val ContentPage.isNeedingMathjax get() = documentable?.documentation?.values ?.flatMap { it.children } .orEmpty() .any { (it as? CustomWrapperTag)?.name == ANNOTATION } }
  16. Plugin dependencies class MyPlugin : DokkaPlugin() { val transformer by

    extending { CoreExtensions.pageTransformer with MyTransformer order { after(plugin<Synthetic>.generateSyntheticFunction) } } val myrendererHelper by extending { plugin<HelpersPlugin>.renderer with MyRendererHelper } }