-
-
Notifications
You must be signed in to change notification settings - Fork 8
Description
One of the most important benefits of using Papercraft for generating HTML is the fact that any piece of a template can be extracted into a component, or partial, or a layout, letting the developer create abstractions as fits their context:
Card = ->(title, text) {
div(class: "card") {
h3(title)
p(text)
}
}
template = -> {
div {
Card("Foo", "foofoofoo")
Card("Bar", "barbarbar")
}
}
Papercraft.html(template) #=> "<div>..."While the DX for using components (or layouts) is great, there is some performance penalty involved when using components. This is because when a component is rendered, it is compiled separately (the first time it is used), and a call is made to its compiled template.
Right now, Papercraft is about as fast as (compiled) ERB, and rendering Papercraft components is about as fast as rendering ERB partials. But would it be possible to use components without paying a performance penalty? Would be possible for Papercraft to be faster than ERB, when dealing with complex pages composed of tens or hundreds of components?
Inlining
This is where inlining comes into the picture. Inlining means that instead of making a call to the component template, the component's compiled code is inlined into the template. If we take the example above, this would look something like the following:
template = -> {
div {
div(class: "card") {
h3("Foo")
p("foofoofoo")
}
div(class: "card") {
h3("Bar")
p("barbarbar")
}
}
}Can we inline everything?
For a template to be inlineable, it needs to answer to the following conditions:
- No local var assignments (
LocalVariableWriteNode) - No return/break statements (
BreakNode,ReturnNode) - No rescue statements (
RescueNode) - anything else?
This can be easily determined by searching the AST for those node types.
Implementation
The implementation involves substantial changes to how Papercraft templates are compiled. Right now, template compilation is done in two phases: a. AST mutation, b. conversion of AST to code. The second phase involves a lot of custom code generation as regards the parts of the code that emit HTML snippets, and is quite tricky to navigate and maintain.
A better approach would be to switch to a single phase, where the HTML emitting code is synthesized as AST nodes directly when performing the AST mutation. Then, a simple conversion of the mutated AST back to source code would be sufficient.
Because we're dealing exclusively with AST's, inlining becomes much much simpler. It's practically a matter doing a preliminary mutation of the AST where each CallNode representing a call to a component is replaced with the corresponding component's AST (with variable substitution, see below). This can be done recursively if we're dealing with nested components.
We then proceed with a second pass where we mutate all HTML-generating CallNodes into HTML emitting nodes.
Variable Substitution
One aspect that needs to be considered is how to do variable substitution in the inlined code. Since templates are parametric, this means that whenever we encounter a LocalVariableReadNode we'll need to replace it with the corresponding argument value given at the call site. The variable substitution algorithm should be able to deal with situations where there are multiple levels of nested components.
Plan of Action
-
Reimplement the Sirop sourcifier (responsible for converting an AST into code). It has two problems: a. it cannot deal with synthetic AST nodes (i.e. nodes without a
location); b. because it uses each node'slocationinformation in order to preserve the original whitespace, in conjunction with the custom HTML emitting code, leads to compiled code that looks really strange in terms of indentation, or with lots of empty lines.So the idea is to emit code based solely on the node type and attributes, with automatic indentation according to best practices.
-
Implement Sirop quote/unquote DSL for generating code: Implement
quote/unquotecode generation DSL sirop#1 -
Reimplement generation of HTML emitting code using Sirop
quote/unquoteDSL. -
Implement template inlining by introducing a preliminary AST mutation pass where each call to a component is replaced with the component's AST, as discussed above.