|
| 1 | +# Mustachio |
| 2 | + |
| 3 | +_Mustachio_ is a code generation-based Mustache render system designed with |
| 4 | +Dartdoc's needs in mind. |
| 5 | + |
| 6 | +## Mustache background |
| 7 | + |
| 8 | +[Mustache templating][] takes a _context object_ and renders it into a |
| 9 | +_template_. For example, an instance of the Library class can be rendered into |
| 10 | +the `library.html` template file. Mustache is primarily a templating syntax, |
| 11 | +where tags specify how _keys_ on the context object are rendered into the |
| 12 | +template. For example, consider the following template: |
| 13 | + |
| 14 | +```mustache |
| 15 | +<h1>{{ name }}</h1> |
| 16 | +{{ #hasDetails }} |
| 17 | +<ul> |
| 18 | + {{ #details }} |
| 19 | + <li>{{ text }}</li> |
| 20 | + {{ /details }} |
| 21 | +</ul> |
| 22 | +{{ /hasDetails }} |
| 23 | +``` |
| 24 | + |
| 25 | +Mustache specifies that `{{ name }}` represents a _variable_ tag, where the |
| 26 | +value of the context object's `name` property is interpolated into the |
| 27 | +template. `{{ #hasDetails }}` and `{{ #details }}` each specify a _section_ tag, |
| 28 | +where the template content between `{{ #hasDetails }}` and `{{ /hasDetails }}` |
| 29 | +is rendered zero, one, or multiple times, possibly with a new context object, |
| 30 | +depending on the value of the context object's `hasDetails` property. The catch |
| 31 | +is in how a Dart program can access a property of an object via a |
| 32 | +runtime-derived String name. |
| 33 | + |
| 34 | +The two popular Mustache packages for Dart ([mustache][] and [mustache4dart][]) |
| 35 | +each use [mirrors][], which is slow, and whose future is unknown. The |
| 36 | +[mustache_template][] package avoids mirrors by restricting context objects to |
| 37 | +just Maps and Lists of values. Neither of these existing solutions were optimal |
| 38 | +for Dartdoc. For a time, dartdoc used the mustache package. When dartdoc creates |
| 39 | +documentation for a package, the majority of the time is spent generating the |
| 40 | +HTML output from package metadata. Benchmarking shows that much time is spent |
| 41 | +using mirrors. |
| 42 | + |
| 43 | +[Mustache templating]: https://mustache.github.io/ |
| 44 | +[mustache]: https://pub.dev/packages/mustache |
| 45 | +[mustache4dart]: https://pub.dev/packages/mustache4dart |
| 46 | +[mirrors]: https://api.dart.dev/stable/dart-mirrors/dart-mirrors-library.html |
| 47 | +[mustache_template]: https://pub.dev/packages/mustache_template |
| 48 | + |
| 49 | +## Motivation |
| 50 | + |
| 51 | +The primary motivation to design a new template rendering solution is to reduce |
| 52 | +the time to generate package documentation. In mid-2020, on a current MacBook |
| 53 | +Pro, it took 12 minutes to generate the Flutter documentation. A solution which |
| 54 | +uses static dispatch instead of mirrors's runtime reflection will be much |
| 55 | +faster. A prototype demonstrated that a new system which parses templates |
| 56 | +ahead-of-time against known context types could render the Flutter documentation |
| 57 | +in 3 minutes. |
| 58 | + |
| 59 | +There are several secondary benefits: |
| 60 | + |
| 61 | +* **Correct static typing** - a solution which uses property access on |
| 62 | + statically typed objects ensures that properties (getters, specifically) |
| 63 | + exist, illuminating typos. |
| 64 | +* **Property usage** - a solution which uses normal property access (calling |
| 65 | + getters) on statically typed objects allows analyzers and IDEs to understand |
| 66 | + when a property is referenced within a template. This is required for |
| 67 | + automated refactoring, finding references, finding definition, and “unused” |
| 68 | + static analysis. |
| 69 | +* **The possibility to restrict API usage** - currently, any custom template |
| 70 | + which a package author writes can walk the entire UML diagram of dartdoc's |
| 71 | + internals, and any types which can be accessed via public properties from the |
| 72 | + primary ModelElement types. This reaches out to include hundreds of types, and |
| 73 | + tens of thousands of properties. A code-generation solution allows dartdoc to |
| 74 | + declare only a supported, restricted subset. |
| 75 | + |
| 76 | +## Mustache's dynamically typed background |
| 77 | + |
| 78 | +Mustache was originally authored as a templating system to be used in |
| 79 | +JavaScript. JavaScript's dynamic typing and use of objects and object properties |
| 80 | +lends itself to simple ideas in parsing Mustache. A renderer can request from |
| 81 | +any object a property with a String name parsed from a Mustache template |
| 82 | +string. JavaScript also has notions of “falsey” and “truthy” which are used in |
| 83 | +rendering sections and inverted sections. |
| 84 | + |
| 85 | +This design is a perfect fit for JavaScript. A Mustache renderer for Dart which |
| 86 | +accesses properties in "the normal way" (not using mirrors, and not using |
| 87 | +dynamic dispatch) requires a non-trivial design. In fact, the two code-generated |
| 88 | +renderer designs provided here each require ahead-of-time knowledge of the |
| 89 | +complete set of types which may be rendered with Mustache templates. No design |
| 90 | +is given for a Mustache renderer which can render arbitrary objects. |
| 91 | + |
| 92 | +## Design overview |
| 93 | + |
| 94 | +### Two rendering methods |
| 95 | + |
| 96 | +When dartdoc generates documentation for a package, it renders context objects |
| 97 | +using Mustache templates. For example, an EnumTemplateData instance (for a |
| 98 | +specific enum) is rendered using a file, `enum.html`. When generating |
| 99 | +documentation for api.dart.dev, pub.dev, and api.flutter.dev, standard, static |
| 100 | +templates are used. When generating documentation for Fuchsia, custom templates |
| 101 | +are used, which are only known and resolved at runtime. |
| 102 | + |
| 103 | +Two code generation-based rendering methods are designed below. The first |
| 104 | +design is for a tool which can generate the code to render objects of specific |
| 105 | +types using runtime-interpreted Mustache template blocks. The second design is |
| 106 | +for a tool which can generate the code to render objects of specific types using |
| 107 | +pre-compiled Mustache template blocks. |
| 108 | + |
| 109 | +**The first tool generates code specific to one set of known types, one renderer |
| 110 | +per type.** Each generated renderer accepts an instance of the appropriate type, |
| 111 | +and a Mustache template block. |
| 112 | + |
| 113 | +The renderers access properties of objects via normal Dart property access |
| 114 | +(without reflection, and without dynamic dispatch), but require complete |
| 115 | +mappings from property names to property accessors for each type. |
| 116 | + |
| 117 | +**The second tool generates code specific to one set of known types, and one |
| 118 | +set of known templates and partials, one renderer per type-template pair.** Each |
| 119 | +generated renderer accepts an instance of the appropriate type. Each template is |
| 120 | +pre-encoded into the appropriate renderer, including the parse tree and all key |
| 121 | +resolution. |
| 122 | + |
| 123 | +The renderers access properties of objects via normal Dart property access |
| 124 | +(without reflection, and without dynamic dispatch). |
| 125 | + |
| 126 | +When using the standard templates to generate documentation, Dartdoc can make |
| 127 | +use of the pre-compiled renderers. When using custom templates to generate |
| 128 | +documentation, Dartdoc must make use of the renderers which interpret template |
| 129 | +blocks at runtime. |
| 130 | + |
| 131 | +## Limitations |
| 132 | + |
| 133 | +Dartdoc's standard templates do not use all features of Mustache. Ergo, |
| 134 | +Mustachio does not support all features of Mustache. Namely: |
| 135 | + |
| 136 | +* no support for Lambda tags |
| 137 | +* no support for Set Delimiter tags |
| 138 | +* no support for resolving Map keys or List indexes |
| 139 | + |
| 140 | +## Parser |
| 141 | + |
| 142 | +The Mustache parser is shared between the two code-generation methods. Parsing |
| 143 | +a Mustache template block of text (from a template or a partial) into a syntax |
| 144 | +tree, without resolving keys, is a solved problem; Mustachio's [Parser] is not |
| 145 | +novel. |
| 146 | + |
| 147 | +The output of this parser is a syntax tree consisting of the following node |
| 148 | +types: |
| 149 | + |
| 150 | +* [Text][] node - plain text as it appears in the template |
| 151 | +* [Variable][] node - a node containing the variable tag key |
| 152 | +* [Section][] node - a node containing the section tag key, whether the section |
| 153 | + tag is inverted, and the syntax tree of the section block |
| 154 | +* [Partial][] node - a node containing the partial tag key |
| 155 | + |
| 156 | +[Parser]: https://github.com/dart-lang/dartdoc/blob/master/lib/src/mustachio/parser.dart |
| 157 | +[Text]: https://github.com/dart-lang/dartdoc/blob/master/lib/src/mustachio/parser.dart#L422 |
| 158 | +[Variable]: https://github.com/dart-lang/dartdoc/blob/master/lib/src/mustachio/parser.dart#L436 |
| 159 | +[Section]: https://github.com/dart-lang/dartdoc/blob/master/lib/src/mustachio/parser.dart#L456 |
| 160 | +[Partial]: https://github.com/dart-lang/dartdoc/blob/master/lib/src/mustachio/parser.dart#L477 |
| 161 | + |
| 162 | +## Generated renderer for a specific type which interprets templates at runtime |
| 163 | + |
| 164 | +TODO(srawlins): Write. |
| 165 | + |
| 166 | +## Generated renderer for a specific type and a static template which pre-compiles the templates |
| 167 | + |
| 168 | +TODO(srawlins): Write. |
0 commit comments