How Edge Works?

This is the deep dive guide on Edge. It is fine if you want to skip this guide and just use Edge in your application. Otherwise, follow along to explore the interesting territory.

Rendering a template is divided into 2 different steps called Compile time and Runtime. All templates are compiled only once and then fetched from in-memory cache.

For development, you can turn off the caching so that they are compiled everytime you make a new change.

Compiler

Let’s understand how the underlying compiler works in Edge. The process is again divided into 2 different steps of generating the AST and converting the values into expressions.

Let’s take a small example on how the @if tag will be compiled.

Template File
@if(username === 'virk')
    <h2> Hello {{ username }} </h2>
@endif

AST

The above template will be processed via Ast class, which generates a nested tree by parsing each line at a time.

There are more properties on each node of AST. I have removed them so that the example output is readable.
Ast of above template
[
  {
    tag: 'if',
    args: 'username === \'virk\'',
    lineno: 1,
    body: [email protected](username === \'virk\')'
    childs: [
      {
        tag: null,
        args: null,
        lineno: 2,
        body: 'Hello {{ username }}'
      }
    ]
  }
]

The AST class understands how tags works in Edge. So it smartly parses them and nests the content inside the tag body as children. This is a recursive operation, which means you can have nested tags inside your templates.

Also, it will keep a track of open tags and if you don’t close a block level tag, the compiler will throw an exception.

Processing Nodes

Once the Abstract Syntax Tree (AST) has been generated, each node of the tree will be processed in sequence.

  1. When a node is a tag, the compiler will call the compile method of the registered tag and it is the responsibility of the tag to understand, validate the process the code.

  2. Otherwise, the line will be interpolated to process the content inside {{ }}.

Each tag gets the lexer object, which can be used to convert any statement into a Javascript expression. Also, tags can tell the lexer to only allow certain expressions. Let’s continue with the if tag example.

class IfTag {

  compile (compiler, lexer, buffer, { body, childs, lineno }) {
    // here body => username === 'virk'
    const expression = lexer.parseRaw(body)
    console.log(expression.tokens) (1)
  }

}
1 Following is the output of the console.log statement.
{
  type: 'binary',
  lhs: {
    value: 'username',
    type: 'source'
  },
  rhs: {
    value: 'virk',
    type: 'string'
  },
  operator: '==='
}

Above generated information is enough to understand the code structure and compile same into to a valid Javascript statement. The ifTag converts these tokens into.

if (this.context.resolve('username') === 'virk') {

}
  1. this.context.resolve is a function available to the template at runtime and is used to fetch the value of a variable from the data object.

  2. Since the rhs is a normal string, we can keep it as it is.

Above is just an example of how lexer can be used to generate expression tokens and compile them into valid Javascript. There are so many moving parts to this. Make sure to check the actual source code of lexer and tags. Also, there is a comprehensive test suite for each part of the code.

Compiler Flow Chart

Below is the flow chart of the compiler.

Compiler Process rbphv3

RunTime

Once a template has been compiled successfully, Edge will run the template by calling the compiled output as a function. The function scope (this) is bound to the Template class and has access to the following object.

  1. this will be the instance of Template Class.

  2. this.context will be the instance of Context Class.

You cannot directly access these classes inside .edge files, since compiler will turn them into something else. For example:

edge file
{{ this.context.resolve('username') }}

Will be converted into

this.callFn(this.accessChild(this.resolve('this'), ['context', 'resolve']), ['username'])

Once Edge will run the above template, something bad will happen for sure, since the meaning of the template has changed into a very complex statement.

this or this.context should be used when you are trying to extend the core by adding your own tags. Not when you are defining templates.

Context

As you would have noticed, the Context class is used quite a lot to run templates. Context is basically a store which has everything your template will need at runtime. For example: It has access to globals, data object, presenter, etc.

The most important piece of code is to know how the context.resolve method works. It will try to resolve a variable by looking at following places, preference given from top to bottom.

  1. Presenter class.

  2. Data/Locals object.

  3. Finally, it will fallback to globals.

Edge Vocabulary

You will hear the following terms quite often when working with Edge templates or reading the documentation. Let’s understand these terms and their meaning.

Term

Description

Tags

Tags are functions that start with @. For example: @if or @include. Tags are the backbone of Edge since they make your templates dynamic. You can also add your own tags.

Globals

Globals are key/value pairs that exist on each template at runtime. It is nice to create globals for shared functionality

Locals

Locals are values that you can pass to a template before running it. Locals can be useful for passing the HTTP request data to your views.

Presenter

Presenter is a Javascript class attached to a given view so that you can keep complex logic inside a Javascript file instead of keeping it inside .edge file.