old render vs new render

Benchmark created on


Setup

class Template2 {
  findvariables = /{(?<variable>[^block:|\/block:].+)}/gmd;
  findblocks = /(?<block>[^\S\r\n]*?{block:(?<tagname>.+)}(?:\s+?)(?<content>[\s\S]*?)(?:\s+?){\/block:\k<tagname>})/gmd;
  structure = [];
  
  constructor(string) {
    this.load(string);
  }

  load(string) {
    this.structure = this.flatten(this.getblocks(string));
  }

  getvariables(string, structure=[]) {
    let matches = [...string.matchAll(this.findvariables)];
    let index = 0;
    if (matches.length == 0) {
      return string;
    }
    for (const match of matches) {
      const variable = match.groups.variable;
      const indices = match.indices.groups.variable;
      const startindex = indices[0] - 1; // remember the curly braces on either side of the variable
      const endindex = indices[1] + 1; 
      
      // get standalone string
      let standalonestring = string.slice(index, startindex);
      // standalone string cannot have any variables in it, since it's already excluded from the findvariables regex matches
      
      structure.push(standalonestring);
      structure.push([[variable]]);
      index = endindex;
    }
    if (index < string.length) {
     structure.push(string.slice(index));
    }
    return structure;
  }
  getblocks(string, structure=[]) {
    let matches = [...string.matchAll(this.findblocks)];
    let index = 0;
    if (matches.length == 0) {
      return this.getvariables(string);
    }
    if (matches.length) {
      for (const match of matches) {
        const groups = match.groups;
        const indices = match.indices.groups;
        const startindex = indices.block[0];
        const endindex = indices.block[1]; 
        
        // check standalone string for variable substitutions
        let standalonestring = this.getvariables(string.slice(index, startindex));
        structure.push(standalonestring);
        structure.push({block: [groups.tagname, this.getblocks(groups.content)]});
        index = endindex;
      }
      if (index < string.length) {
       structure.push(this.getvariables(string.slice(index)));
      }

    } else {
      return this.getvariables(string.slice)
    }
    return structure;
  }



  getFunction2(string) {
    return new Function('d', `for (let p in d) { eval('var ' + p + ' = ' + JSON.stringify(d[p]) + ';' )}; ` + 'return `${(' + string + ' ?? ("{" + "' + string + '" + "}"))}`');
  }
  
  getFunctionBlock(string) {
    return new Function('d', 'return `${(d?.' + string + ' == true)}`');
  }

  flatten(structure=[]) {
    const structure_length = structure.length;
    let output = [];
    for (let i=0; i < structure_length; i++) {
      const item = structure[i];
      if (item && typeof item === "string") {
        output.push(0, item);
        continue;
      } else if (item?.length == 1 && item[0]?.length == 1) {
        output.push(1, this.getFunction2(item[0][0]));
      } else if (item.block) {
        const blockcontent = this.flatten(item.block[1]);
        const len = 3 + blockcontent.length;
        output.push(2, this.getFunctionBlock(item.block[0]), len);
        output = output.concat(blockcontent);
      }
    }
    return output;
  }

  _render(options={}) {
    let output = '';
    const len = this.structure.length;
    const flat = this.structure;
    let i=0;
    while (i < len)
    {
      const item = flat[i];
      const content = flat[i + 1];
      switch (item) {
        case 0:
          output += content;
          i += 2;
          break;
        case 1:
          output += content(options); //print variable name if not found in options
          i+=2;
          break;
        case 2:
          i += content(options) ? 3 : flat[i + 2];
          break;
      }
    }
    return output;
  }
  render(options={}) {
    return this._render.bind(this)(options);
  }
}

class Template {
  findvariables = /{(?<variable>[^block:|\/block:].+)}/gmd;
  findblocks = /(?<block>[^\S\r\n]*?{block:(?<tagname>.+)}(?:\s+?)(?<content>[\s\S]*?)(?:\s+?){\/block:\k<tagname>})/gmd;
  structure = [];
  
  constructor(string) {
    this.load(string);
  }

  load(string) {
    this.structure = this.flatten(this.getblocks(string));
  }

  getvariables(string, structure=[]) {
    let matches = [...string.matchAll(this.findvariables)];
    let index = 0;
    if (matches.length == 0) {
      return string;
    }
    for (const match of matches) {
      const variable = match.groups.variable;
      const indices = match.indices.groups.variable;
      const startindex = indices[0] - 1; // remember the curly braces on either side of the variable
      const endindex = indices[1] + 1; 
      
      // get standalone string
      let standalonestring = string.slice(index, startindex);
      // standalone string cannot have any variables in it, since it's already excluded from the findvariables regex matches
      
      structure.push(standalonestring);
      structure.push([[variable]]);
      index = endindex;
    }
    if (index < string.length) {
     structure.push(string.slice(index));
    }
    return structure;
  }
  getblocks(string, structure=[]) {
    let matches = [...string.matchAll(this.findblocks)];
    let index = 0;
    if (matches.length == 0) {
      return this.getvariables(string);
    }
    if (matches.length) {
      for (const match of matches) {
        const groups = match.groups;
        const indices = match.indices.groups;
        const startindex = indices.block[0];
        const endindex = indices.block[1]; 
        
        // check standalone string for variable substitutions
        let standalonestring = this.getvariables(string.slice(index, startindex));
        structure.push(standalonestring);
        structure.push({block: [groups.tagname, this.getblocks(groups.content)]});
        index = endindex;
      }
      if (index < string.length) {
       structure.push(this.getvariables(string.slice(index)));
      }

    } else {
      return this.getvariables(string.slice)
    }
    return structure;
  }


  getFunction2(string) {
    return new Function('d', 'return `${(d?.' + string + ' ?? ("{" + string + "}"))}`');
  }
  getFunctionBlock(string) {
    return new Function('d', 'return `${(d?.' + string + ' == true)}`');
  }

  flatten(structure=[]) {
    const structure_length = structure.length;
    let output = [];
    for (let i=0; i < structure_length; i++) {
      const item = structure[i];
      if (item && typeof item === "string") {
        output.push(0, item);
        continue;
      } else if (item?.length == 1 && item[0]?.length == 1) {
        output.push(1, item[0][0]);
      } else if (item.block) {
        const blockcontent = this.flatten(item.block[1]);
        const len = 3 + blockcontent.length;
        output.push(2, item.block[0], len);
        output = output.concat(blockcontent);
      }
    }
    return output;
  }

  _render(options={}) {
    let output = '';
    const len = this.structure.length;
    const flat = this.structure;
    let i=0;
    while (i < len)
    {
      const item = flat[i];
      const string = flat[i + 1];
      switch (item) {
        case 0:
          output += string;
          i += 2;
          break;
        case 1:
          output += options?.[string] ?? '{' + string + '}'; //print variable name if not found in options
          i+=2;
          break;
        case 2:
          i += options?.[string] ? 3 : flat[i + 2];
          break;
      }
    }
    return output;
  }
  render(options={}) {
    return this._render.bind(this)(options);
  }
}

const string = `
				{block:ShowAvatar}
            <div class="portrait {AvatarShape}">
                <img src="{PortraitURL_128}" />
            </div>
            <div class="name">{Title}</div>
				{/block:ShowAvatar}`;
				
const template = new Template(string);
const template2 = new Template2(string);


Test runner

Ready to run.

Testing in
TestOps/sec
old render
template.render({
  "ShowAvatar": true,
  "AvatarShape": "round",
  "PortraitURL_128": "https://somethingsomething.png",
  "Title": "Aaron's Blog"
})
ready
new render
template2.render({
  "ShowAvatar": true,
  "AvatarShape": "round",
  "PortraitURL_128": "https://somethingsomething.png",
  "Title": "Aaron's Blog"
})
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.