Module: lib/compiler/ir/build

Builds the template IR from template source.

This runs on the template as the developer wrote it -- after declarations and imports are removed and scoped-CSS classes are applied, but before any directive rewriting. That ordering is the whole point: reading <@for item in items> directly is what lets the compiler record that it is a loop over items binding item, instead of rewriting it into a <template> element and asking the runtime to work that out again.

What it refuses

A construct the IR does not model yet aborts the build of that template with a IRRefusal naming the construct. The caller keeps the legacy path for that component and the build reports it. There is no partial IR, for the same reason there is no partial render program: two descriptions of one template disagree eventually, and the disagreement is unattributable.

Source:

Members

(inner, constant) DIRECTIVE_BINDINGS :Map:.<string:, string:>

Directive attributes that become bindings rather than markup.

Type:
  • Map:.<string:, string:>
Source:

(inner, constant) UNMODELLED_ATTRS :Array:.<Array:.<string:>>

Attributes whose presence means the element needs machinery the IR does not model, mapped to the reason reported.

Type:
  • Array:.<Array:.<string:>>
Source:

(inner, constant) UNMODELLED_TAGS :Map:.<string:, string:>

Directive tags the IR does not model yet, mapped to the reason reported.

Type:
  • Map:.<string:, string:>
Source:

Methods

(static) buildTemplateIR(template, optionsopt) → {Object}

Builds the IR for a template.

Parameters:
Name Type Attributes Description
template string

The template source, before directive rewriting.

options object <optional>

Build options.

Properties
Name Type Attributes Description
voidTags Array:.<string:> <optional>

Project-specific void tag names.

Source:
Returns:

The root fragment, or the reason the template could not be represented.

Type
Object

(static) parseForHeader(header) → {Object}

Parses a <@for> header into its parts.

Two binding forms, both pre-existing and both preserved exactly:

  • item in list binds each element to item.
  • [a, b] in pairs destructures each element -- which is an array -- into a and b. It does not mean (item, index), however much it looks like the JavaScript it resembles.

Either form also binds index implicitly, which is how the loop index has always been reached. Both facts are load-bearing for existing applications, so they are read out of the previous implementation rather than redesigned here; changing either belongs in its own change, with its own migration.

Parameters:
Name Type Description
header string

The raw header text.

Source:
Throws:

When the header is not a loop header.

Type
IRRefusal
Returns:

The parts.

Type
Object

(static) splitInterpolations(source) → {Array:.<{expr:: (string:|null:), raw:: boolean:, value:: string:}>}

Splits text into literal and expression segments, in order.

Parameters:
Name Type Description
source string

The source text.

Source:
Returns:

The segments.

Type
Array:.<{expr:: (string:|null:), raw:: boolean:, value:: string:}>

(inner) assertHeaderNotTruncated(node, test)

Rejects a conditional header that the tag scan cut in half.

<@if count > 3> cannot be read unambiguously: the > that means "greater than" and the > that means "end of tag" are the same character, and nothing in the surrounding text distinguishes them. Bracket depth rescues <@for x in xs.filter(a => a.n > 1)> because the comparison is inside a call; a bare comparison has nothing to hide behind.

So the compiler does not guess. A header that was truncated leaves the rest of the expression as the first text node of the branch body -- " 3>" above -- and that shape is what this detects. The author gets the parenthesised form, which the scanner reads correctly, rather than a condition that silently tests the wrong thing.

Parameters:
Name Type Description
node object

The parsed directive node.

test string

The header text as scanned.

Source:
Throws:

When the header was cut at a comparison.

Type
IRRefusal

(inner) buildAttributes(node) → {Object}

Builds an element's bindings and events from its attribute map.

Parameters:
Name Type Description
node object

The parsed HTML node.

Source:
Throws:

When an attribute needs machinery the IR does not model.

Type
IRRefusal
Returns:

The element parts.

Type
Object

(inner) buildConditional(siblings, index, buildChildren) → {Object}

Collects an <@if> chain starting at index, consuming its continuations.

Parameters:
Name Type Description
siblings Array:.<object:>

The sibling list being walked.

index number

Index of the <@if> node.

buildChildren function

Recursive child builder.

Source:
Returns:

The conditional node and the index to resume at.

Type
Object

(inner) buildDefer(node, when, buildChildren) → {object}

Builds a deferred block from a <@defer> element.

<@placeholder> is a sibling of the deferred content rather than a wrapper around it, so it is separated out here the same way <@empty> is separated from a loop body.

Parameters:
Name Type Description
node object

The parsed <@defer> node.

when string

The resolved trigger.

buildChildren function

Recursive child builder.

Source:
Returns:

The defer node.

Type
object

(inner) buildIteration(node, buildChildren) → {object}

Builds an iteration node from a <@for> element.

Parameters:
Name Type Description
node object

The parsed <@for> node.

buildChildren function

Recursive child builder.

Source:
Returns:

The iteration node.

Type
object

(inner) buildProps(node) → {Array:.<object:>}

Builds the props of a child component from its attribute map.

Parameters:
Name Type Description
node object

The parsed HTML node.

Source:
Returns:

Prop descriptors.

Type
Array:.<object:>

(inner) findTopLevelWord(source, word) → {number}

Finds the offset of a top-level occurrence of a keyword in an expression.

"Top level" means outside quotes and outside every bracket pair, so the in of <@for k in Object.keys(map)> is found and the in of an x in y written inside a call's arguments is not.

Parameters:
Name Type Description
source string

The text to scan.

word string

The keyword to find.

Source:
Returns:

The offset, or -1.

Type
number

(inner) hasInterpolation(value) → {boolean}

Whether a string contains a template interpolation.

Parameters:
Name Type Description
value string

The text to test.

Source:
Returns:

True when it contains {{ }} or {{{ }}}.

Type
boolean

(inner) isComponentTag(tag) → {boolean}

Whether a tag name refers to a child component rather than an element.

Parameters:
Name Type Description
tag string

The tag name as written.

Source:
Returns:

True for a PascalCase reference.

Type
boolean

(inner) parseEventName(name) → {Object}

Parses an event attribute name into its event and modifiers.

Parameters:
Name Type Description
name string

The attribute name, including the leading @.

Source:
Returns:

The parsed parts.

Type
Object

(inner) splitListAndKey(header) → {Object}

Strips a trailing key="..." clause from a <@for> header.

Parameters:
Name Type Description
header string

The header text after the list expression.

Source:
Returns:

The list source and the key source.

Type
Object

(inner) unwrapDirectiveValue(value) → {string}

Reads a directive attribute's expression, with or without {{ }} around it.

Both spellings are documented and both appear in the wild:

<p data-ax-show="isOpen">
<p data-ax-style="{{ { fontWeight: bold ? '700' : '400' } }}">

The braces are how an author writes an object literal without it looking like an attribute value, so they are stripped here rather than handed to the expression parser, which would read {{ as the start of a block.

Parameters:
Name Type Description
value string

The attribute value as written.

Source:
Returns:

The expression source.

Type
string