squirrelly
Version:
Simple and powerful template engine that supports helpers, partials, filters, native code, and Express.
177 lines (171 loc) • 6.42 kB
JavaScript
import { regEx, setup, replaceParamHelpers } from './regexps'
import nativeHelpers from './nativeHelpers'
import { parseFiltered } from './filters'
import P from './partials'
function Compile (str) {
var lastIndex = 0 // Because lastIndex can be complicated, and this way the minifier can minify more
var funcStr = "var tR='';" // This will be called with Function() and returned
var helperArray = [] // A list of all 'outstanding' helpers, or unclosed helpers
var helperNumber = -1
var helperAutoId = 0 // Squirrelly automatically generates an ID for helpers that don't have a custom ID
var helperContainsBlocks = {} // If a helper contains any blocks, helperContainsBlocks[helperID] will be set to true
var m
function addString (indx) {
if (lastIndex !== indx) {
funcStr +=
"tR+='" +
str
.slice(lastIndex, indx)
.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'") +
"';"
}
}
setup()
while ((m = regEx.exec(str)) !== null) {
addString(m.index) // Add the string between the last tag (or start of file) and the current tag
lastIndex = m[0].length + m.index
if (m[1]) {
// It's a global ref. p4 = filters
funcStr += 'tR+=' + globalRef(m[1], m[4]) + ';'
} else if (m[3]) {
// It's a helper ref. p2 = id (with ':' after it) or path, p4 = filters
funcStr += 'tR+=' + helperRef(m[3], m[2], m[4]) + ';'
} else if (m[5]) {
// It's a helper oTag. p6 parameters, p7 id
var id = m[7]
if (id === '' || id === null) {
id = helperAutoId
helperAutoId++
}
var native = nativeHelpers.hasOwnProperty(m[5]) // true or false
helperNumber += 1
var params = m[6] || ''
params = replaceParamHelpers(params)
if (!native) {
params = '[' + params + ']'
}
var helperTag = {
name: m[5],
id: id,
params: params,
native: native
}
helperArray[helperNumber] = helperTag
if (native) {
funcStr += nativeHelpers[m[5]].helperStart(params, id)
lastIndex = regEx.lastIndex // the changeTags function sets lastIndex already
} else {
funcStr +=
'tR+=Sqrl.H.' +
m[5] +
'(' +
params +
',function(hvals){var hvals' +
id +
"=hvals;var tR='';"
}
} else if (m[8]) {
// It's a helper cTag.
var mostRecentHelper = helperArray[helperNumber]
if (mostRecentHelper && mostRecentHelper.name === m[8]) {
helperNumber -= 1
if (mostRecentHelper.native === true) {
funcStr += nativeHelpers[mostRecentHelper.name].helperEnd(
mostRecentHelper.params,
mostRecentHelper.id
)
} else {
if (helperContainsBlocks[mostRecentHelper.id]) {
funcStr += 'return tR}});'
} else {
funcStr += 'return tR});'
}
}
} else {
console.error("Helper beginning & end don't match.")
}
} else if (m[9]) {
// It's a helper block.
var parent = helperArray[helperNumber]
if (parent.native) {
var nativeH = nativeHelpers[parent.name]
if (nativeH.blocks && nativeH.blocks[m[9]]) {
funcStr += nativeH.blocks[m[9]](parent.id)
lastIndex = regEx.lastIndex // Some native helpers set regEx.lastIndex
} else {
console.warn(
"Native helper '%s' doesn't accept that block.",
parent.name
)
}
} else {
if (!helperContainsBlocks[parent.id]) {
funcStr +=
'return tR},{' +
m[9] +
':function(hvals){var hvals' +
parent.id +
"=hvals;var tR='';"
helperContainsBlocks[parent.id] = true
} else {
funcStr +=
'return tR},' +
m[9] +
':function(hvals){var hvals' +
parent.id +
"=hvals;var tR='';"
}
}
} else if (m[10]) {
// It's a self-closing helper
var innerParams = m[11] || ''
innerParams = replaceParamHelpers(innerParams)
if (m[10] === 'include') {
// This code literally gets the template string up to the include self-closing helper,
// adds the content of the partial, and adds the template string after the include self-closing helper
var preContent = str.slice(0, m.index)
var endContent = str.slice(m.index + m[0].length)
var partialParams = innerParams.replace(/'|"/g, '') // So people can write {{include(mypartial)/}} or {{include('mypartial')/}}
var partialContent = P[partialParams]
str = preContent + partialContent + endContent
lastIndex = regEx.lastIndex = m.index
} else if (
nativeHelpers.hasOwnProperty(m[10]) &&
nativeHelpers[m[10]].hasOwnProperty('selfClosing')
) {
funcStr += nativeHelpers[m[10]].selfClosing(innerParams)
lastIndex = regEx.lastIndex // changeTags sets regEx.lastIndex
} else {
funcStr += 'tR+=Sqrl.H.' + m[10] + '(' + innerParams + ');' // If it's not native, passing args to a non-native helper
}
}
/* eslint-disable no-inner-declarations */
function globalRef (refName, filters) {
return parseFiltered('options.' + refName, filters)
}
function helperRef (name, id, filters) {
var prefix
if (typeof id !== 'undefined') {
if (/(?:\.\.\/)+/g.test(id)) {
// Test if the helper reference is prefixed with ../
prefix = helperArray[helperNumber - id.length / 3 - 1].id
} else {
prefix = id.slice(0, -1)
}
return parseFiltered('hvals' + prefix + '.' + name, filters)
} // Implied 'else'
return parseFiltered('hvals.' + name, filters)
}
/* eslint-enable no-inner-declarations */
}
addString(str.length) // Add the string from the last tag-close to the end of the file, if there is one
funcStr += 'return tR'
var func = new Function( //eslint-disable-line
'options',
'Sqrl',
funcStr.replace(/\n/g, '\\n').replace(/\r/g, '\\r')
)
return func
}
export default Compile