Skip to content

Commit

Permalink
perf: switch to tempura for templating
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeRalphson committed Mar 24, 2023
1 parent 4ce644e commit 3a5d6a1
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 48 deletions.
10 changes: 6 additions & 4 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"jquery": true
},
"parserOptions": {
"ecmaVersion": 2017
"ecmaVersion": 2017,
"sourceType": "module"
},
"extends": "eslint:recommended",
"rules": {
Expand Down Expand Up @@ -37,7 +38,7 @@
"callback-return": "off",
"camelcase": "off",
"class-methods-use-this": "error",
"comma-dangle": "error",
"comma-dangle": "warn",
"comma-spacing": "off",
"comma-style": [
"error",
Expand Down Expand Up @@ -131,6 +132,7 @@
"no-extra-parens": "off",
"no-floating-decimal": "error",
"no-global-assign": "error",
"no-cond-assign": "warn",
"no-implicit-globals": "error",
"no-implied-eval": "error",
"no-inline-comments": "off",
Expand All @@ -151,7 +153,7 @@
"no-multi-spaces": "off",
"no-multi-str": "error",
"no-multiple-empty-lines": "error",
"no-negated-condition": "error",
"no-negated-condition": "warn",
"no-nested-ternary": "off",
"no-new": "error",
"no-new-func": "error",
Expand Down Expand Up @@ -252,7 +254,7 @@
"spaced-comment": "off",
"strict": "error",
"symbol-description": "error",
"template-curly-spacing": "error",
"template-curly-spacing": "warn",
"unicode-bom": [
"error",
"never"
Expand Down
59 changes: 30 additions & 29 deletions src/_includes/card.html
Original file line number Diff line number Diff line change
@@ -1,56 +1,57 @@
<script type="text/dot-template">
<script type="text/tempura">
{% raw %}
{{#expect it}}
<div class="card">
{{? it.classes}}
<span class="{{=it.classes}}" title="{{=it.flashTitle}}"><strong>{{=it.flashText}}</strong></span>
{{??}}
{{#if it.classes}}
<span class="{{{it.classes}}}" title="{{{it.flashTitle}}}"><strong>{{{it.flashText}}}</strong></span>
{{#else}}
<span class="spacer"></span>
{{?}}
{{/if}}
<header>
<h2 title="{{=it.info.title }}">
{{? it.externalUrl }}
<a href="{{=it.externalUrl }}" target="_blank">{{=it.info.title }}</a>
{{??}}
{{=it.info.title }}
{{?}}
<h2 title="{{{it.info.title}}}">
{{#if it.externalUrl }}
<a href="{{{it.externalUrl}}}" target="_blank">{{it.info.title}}</a>
{{#else}}
{{it.info.title}}
{{/if}}
</h2>
</header>
<section class="api-body">
<img src="{{=it.logo.url || 'assets/images/no-logo.svg'}}" alt="{{=it.info.title }} API logo" style="background-color: {{=it.logo.backgroundColor || 'transparent'}}" class="api-logo">
<p>{{=it.cardDescription}}</p>
<img src="{{it.logo.url || 'assets/images/no-logo.svg'}}" alt="{{it.info.title }} API logo" style="background-color: {{it.logo.backgroundColor || 'transparent'}}" class="api-logo">
<p>{{it.cardDescription}}</p>
</section>
<footer>
<h3> OpenAPI: </h3>
<h4>Preferred Version - {{=it.preferred}}</h4>
<h4>Preferred Version - {{it.preferred}}</h4>
<ul class="preferred-api">
<li><a href="{{=it.api.swaggerUrl}}" target="_blank" >JSON</a></li>
<li><a href='{{=it.api.swaggerYamlUrl}}' target="_blank" >YAML</a></li>
<li><a href="{{=it.origUrl}}" target='_blank'>Orig</a></li>
<li><a href='https://redocly.github.io/redoc/?url={{=it.api.swaggerUrl}}' target="_blank" >Docs</a></li>
<li><a href="{{it.api.swaggerUrl}}" target="_blank" >JSON</a></li>
<li><a href='{{it.api.swaggerYamlUrl}}' target="_blank" >YAML</a></li>
<li><a href="{{it.origUrl}}" target='_blank'>Orig</a></li>
<li><a href='https://redocly.github.io/redoc/?url={{it.api.swaggerUrl}}' target="_blank" >Docs</a></li>
</ul>
{{? it.versions }}
{{#if it.versions }}
<details>
<summary><h4>All Versions</h4></summary>
<ul class="other-versions">
{{~it.versions :version:index}}
{{#each it.versions as version}}
<li>
<span>{{=version.version}}</span>
<span>{{version.version}}</span>
<ul>
<li><a href="{{=version.swaggerUrl}}" target="_blank" >JSON</a></li>
<li><a href="{{=version.swaggerYamlUrl}}" target='_blank'>YAML</a></li>
<li><a href='https://redocly.github.io/redoc/?url={{=version.swaggerUrl}}' target="_blank" >Docs</a></li>
<li><a href="{{version.swaggerUrl}}" target="_blank" >JSON</a></li>
<li><a href="{{version.swaggerYamlUrl}}" target='_blank'>YAML</a></li>
<li><a href='https://redocly.github.io/redoc/?url={{version.swaggerUrl}}' target="_blank" >Docs</a></li>
</ul>
</li>
{{~}}
{{/each}}
</ul>
</details>
{{?}}
{{/if}}
<details>
<summary><h4>Tools</h4></summary>
<ul class="tools">
{{~it.integrations :integration:index}}
<li><a href="{{=integration.template}}" target="_blank" >{{=integration.text}}</a></li>
{{~}}
{{#each it.integrations as integration}}
<li><a href="{{integration.template}}" target="_blank" >{{integration.text}}</a></li>
{{/each}}
</ul>
</details>
</footer>
Expand Down
2 changes: 0 additions & 2 deletions src/_layouts/apis-page.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@
<!-- JS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.7.0/marked.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dot/1.0.3/doT.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/slideout/0.1.12/slideout.js"></script>
<!-- inject:js -->
<script src="/assets/javascript/apis.js"></script>
<script src="/assets/javascript/main.js"></script>
<!-- endinject -->

Expand Down
13 changes: 6 additions & 7 deletions src/assets/javascript/apis.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
'use strict';
import * as tempura from "./tempura.js";

const dummy = {
loading: {
Expand Down Expand Up @@ -128,16 +128,15 @@ CardModel.prototype.fromAPIs = function(name, apis) {
return this;
};

if (window.$) {
$(document).ready(function () {
var cardTemplateSrc = document.querySelector('script[type="text/dot-template"]').innerText;
var cardTemplate = window.doT.compile(cardTemplateSrc);
export function loadAPIs() {
var cardTemplateSrc = document.querySelector('script[type="text/tempura"]').innerText;
var cardTemplate = tempura.compile(cardTemplateSrc);

var updateCards = function(data) {
var fragment = $(document.createDocumentFragment());
$.each(data, function (name, apis) {
var model = new CardModel().fromAPIs(name, apis);
var view = cardTemplate(model);
var view = cardTemplate({it:model});
fragment.append($(view));
});

Expand Down Expand Up @@ -232,5 +231,5 @@ if (window.$) {
$('#search-input').focus();
});

});
}

2 changes: 0 additions & 2 deletions src/assets/javascript/main.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
'use strict';

function domReady(cb) {
document.addEventListener("DOMContentLoaded", cb, false);
}
Expand Down
138 changes: 138 additions & 0 deletions src/assets/javascript/tempura.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
const ESCAPE = /[&"<]/g, CHARS = {
'"': '&quot;',
'&': '&amp;',
'<': '&lt',
};

const ENDLINES = /[\r\n]+$/g;
const CURLY = /{{{?\s*([\s\S]*?)\s*}}}?/g;
const ARGS = /([a-zA-Z$_][^\s=]*)\s*=\s*((["`'])(?:(?=(\\?))\4.)*?\3|{[^}]*}|\[[^\]]*]|\S+)/g;

// $$1 = escape()
// $$2 = extra blocks
// $$3 = template values
function gen(input, options) {
options = options || {};

let char, num, action, tmp;
let last = CURLY.lastIndex = 0;
let wip='', txt='', match, inner;

let extra=options.blocks||{}, stack=[];
let initials = new Set(options.props||[]);

function close() {
if (wip.length > 0) {
txt += (txt ? 'x+=' : '=') + '`' + wip + '`;';
} else if (txt.length === 0) {
txt = '="";'
}
wip = '';
}

while (match = CURLY.exec(input)) {
wip += input.substring(last, match.index).replace(ENDLINES, '');
last = match.index + match[0].length;

inner = match[1].trim();
char = inner.charAt(0);

if (char === '!') {
// comment, continue
} else if (char === '#') {
close();
[, action, inner] = /^#\s*(\w[\w\d]+)\s*([^]*)/.exec(inner);

if (action === 'expect') {
inner.split(/[\n\r\s\t]*,[\n\r\s\t]*/g).forEach(key => {
initials.add(key);
});
} else if (action === 'var') {
num = inner.indexOf('=');
tmp = inner.substring(0, num++).trim();
inner = inner.substring(num).trim().replace(/[;]$/, '');
txt += `var ${tmp}=${inner};`;
} else if (action === 'each') {
num = inner.indexOf(' as ');
stack.push(action);
if (!~num) {
txt += `for(var i=0,$$a=${inner};i<$$a.length;i++){`;
} else {
tmp = inner.substring(0, num).trim();
inner = inner.substring(num + 4).trim();
let [item, idx='i'] = inner.replace(/[()\s]/g, '').split(','); // (item, idx?)
txt += `for(var ${idx}=0,${item},$$a=${tmp};${idx}<$$a.length;${idx}++){${item}=$$a[${idx}];`;
}
} else if (action === 'if') {
txt += `if(${inner}){`;
stack.push(action);
} else if (action === 'elif') {
txt += `}else if(${inner}){`;
} else if (action === 'else') {
txt += `}else{`;
} else if (action in extra) {
if (inner) {
tmp = [];
// parse arguments, `defer=true` -> `{ defer: true }`
while (match = ARGS.exec(inner)) tmp.push(match[1] + ':' + match[2]);
inner = tmp.length ? '{' + tmp.join() + '}' : '';
}
inner = inner || '{}';
tmp = options.async ? 'await ' : '';
wip += '${' + tmp + '$$2.' + action + '(' + inner + ',$$2)}';
} else {
throw new Error(`Unknown "${action}" block`);
}
} else if (char === '/') {
action = inner.substring(1);
inner = stack.pop();
close();
if (action === inner) txt += '}';
else throw new Error(`Expected to close "${inner}" block; closed "${action}" instead`);
} else if (match[0].charAt(2) === '{') {
wip += '${' + inner + '}'; // {{{ raw }}}
} else {
wip += '${$$1(' + inner + ')}';
}
}

if (stack.length > 0) {
throw new Error(`Unterminated "${stack.pop()}" block`);
}

if (last < input.length) {
wip += input.substring(last).replace(ENDLINES, '');
}

close();

tmp = initials.size ? `{${ [...initials].join() }}=$$3,x` : ' x';
return `var${tmp + txt}return x`;
}

export function esc(value) {
if (typeof value !== 'string') return value;
let last=ESCAPE.lastIndex=0, tmp=0, out='';
while (ESCAPE.test(value)) {
tmp = ESCAPE.lastIndex - 1;
out += value.substring(last, tmp) + CHARS[value[tmp]];
last = tmp + 1;
}
return out + value.substring(last);
}

export function compile(input, options={}) {
return new (options.async ? (async()=>{}).constructor : Function)(
'$$1', '$$2', '$$3', gen(input, options)
).bind(0, options.escape || esc, options.blocks);
}

export function transform(input, options={}) {
return (
options.format === 'cjs'
? 'var $$1=require("tempura").esc;module.exports='
: 'import{esc as $$1}from"tempura";export default '
) + (
options.async ? 'async ' : ''
) + 'function($$3,$$2){'+gen(input, options)+'}';
}
10 changes: 6 additions & 4 deletions src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,20 @@ support: true
{% include 'card.html' %}

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script>
<script type="module">

import * as apis from "./assets/javascript/apis.js";

$(document).ready(function(){
var newData = false;
if (window.location.href.indexOf('nd=')>=0) newData = true;
$.ajax({
type: "GET",
url: (newData ? "https://raw.githubusercontent.com/APIs-guru/openapi-directory/gh-pages/v2/metrics.json" : "https://api.apis.guru/v2/metrics.json"),
url: "https://api.apis.guru/v2/metrics.json",
dataType: 'json',
cache: true,
success: function (data) {
$('#numAPIs').text(data.numAPIs.toLocaleString());
}
});
apis.loadAPIs();
});
</script>

0 comments on commit 3a5d6a1

Please sign in to comment.