Parser to convert edge templates to invokable functions
This repo is the parser to convert edge templates to a self invoked Javascript function.
Install the package from npm registry as follows:
npm i edge-parser
# yarn
yarn add edge-parser
and then use it as follows
import { Parser, EdgeBuffer, Stack } from 'edge-parser'
const filename = 'eval.edge'
const parser = new Parser({}, new Stack(), {
statePropertyName: 'state',
escapeCallPath: 'escape',
toAttributesCallPath: 'toAttributes',
})
const buffer = new EdgeBuffer(filename, {
outputVar: 'out',
rethrowCallPath: 'reThrow'
})
parser
.tokenize('Hello {{ username }}', { filename })
.forEach((token) => parser.processToken(token, buffer))
const output = buffer.flush()
console.log(output)
-
filename
is required to ensure that exceptions stack traces point back to the correct filename. -
statePropertyName
is the variable name from which the values should be accessed. For example:{{ username }}
will be compiled asstate.username
. Leave it to empty, if state is not nested inside an object. -
escapeCallPath
Reference to theescape
method for escaping interpolation values. For example:{{ username }}
will be compiled asescape(state.username)
. Theescape
method should escape only strings and return the other data types as it is. -
toAttributesCallPath
: Reference to the function that will convert an object to HTML attributes. -
outputVar
is the variable name that holds the output of the compiled template. -
rethrowCallPath
Reference to thereThrow
method to raise the template exceptions with the current$filename
and$lineNumber
. Check the following compiled output to see how this function is called.
Compiled output
let out = ''
let $lineNumber = 1
let $filename = 'eval.edge'
try {
out += 'Hello '
out += `${escape(state.username)}`
} catch (error) {
reThrow(error, $filename, $lineNumber)
}
return out
You can wrap the compiled output inside a function and invoke it as follows
/**
* Convert string to a function
*/
const fn = new Function('state, escape, reThrow', output)
/**
* Template state
*/
const state = { username: 'virk' }
/**
* Escape function
*/
function escape(value: any) {
return value
}
/**
* Rethrow function
*/
function reThrow(error: Error) {
throw error
}
console.log(fn(state, escape, reThrow))
Along with parsing the main template, the parser also exposes the API, that tags can use to selectively parse the content of a tag.
Parses a string as a Javascript expression. The output is a valid Estree expression
The following example returns a BinaryExpression
const loc = {
start: { line: 1, col: 1 },
end: { line: 1, col: 1 },
}
const filename = 'eval.edge'
parser.utils.generateAST('2 + 2', loc, filename)
Transform the acorn AST and make it compatible with Edge runtime. This method mutates the inner nodes of the original AST.
const loc = {
start: { line: 1, col: 1 },
end: { line: 1, col: 1 },
}
const filename = 'eval.edge'
parser.utils.transformAst(parser.utils.generateAST('2 + 2', loc, filename), filename)
Returns an array of lexer tokens for the given template. The method is a shortcut to self import the lexer module and then generating tokens.
const tokens = parser.tokenize('Hello {{ username }}', {
filename: 'eval.edge',
})
Output
[
{
"type": "raw",
"line": 1,
"value": "Hello "
},
{
"type": "mustache",
"filename": "eval.edge",
"loc": {
"start": {
"line": 1,
"col": 8
},
"end": {
"line": 1,
"col": 20
}
},
"properties": {
"jsArg": " username "
}
}
]
Convert edge or acorn expression back to a string. This is helpful, when you mutate some nodes inside the expression and now want a valid Javascript string out of it.
const expression = parser.utils.generateAST(
'2 + 2',
{
start: { line: 1, col: 1 },
end: { line: 1, col: 1 },
},
'eval.edge'
)
expression.left.value = 3
parser.utils.stringify(expression) // returns 3 + 2
You will often find yourself using this method as a tag author, when you want to recursively process all children of your tag
const byPass = {
block: true,
seekable: false,
name: 'bypass',
compile(parser, buffer, token) {
token.children.forEach((child) => parser.processToken(child, buffer))
},
}
and then use it as
@bypass
Hello {{ username }}
@endbypass
The following expressions are supported by the parser. Can you also access the list of supported expressions as
import { expressions } from 'edge-parser'
The identifier are prefixed with state.
In following statement username
is the identifier
Hello {{ username }}
A string literal
Hello {{ 'Guest' }}
The [1, 2, 3, 4]
is an array expression.
Evens are {{
[1, 2, 3, 4].filter((num) => num % 2 === 0)
}}
The { username: 'virk' }
is an Object expression
{{ toJSON({ username: 'virk' }) }}
Following are examples of UnaryExpression
.
{{ typeof(username) }}
{{ !!username }}
Here {{ 2 + 2 }}
is the binary expression
{{ 2 + 2 }} = 4
Following is the example of LogicalExpression
.
{{ username || admin.username }}
{{ username.toUpperCase() }}
{{ username ? username : 'Guest' }}
{{ upper(username) }}
Sequence is not supported in mustache blocks and instead used inside tags. For example:
Everything inside ()
is a sequence expression.
@component('button', text = 'Submit', type = 'Primary')
{{ Hello `${username}` }}
{{
users.map((user) => {
return user.username
})
}}
{{ await foo() }}
{{ function foo () {} }}
Here the map
callback is the block statement
{{
users.map(() => {})
}}
Support for optional chaining
{{ user?.username }}
{{ new User() }}
In the following example return
keyword is a return statement
users.map((user) => {
return user.username
})
Support for the this keyword
{{ this.state }}
Support for the spread element
{{ [...users] }}