Skip to content

Commit 37d9691

Browse files
committed
perf: switch to tempura for templating
1 parent 0d1329d commit 37d9691

File tree

5 files changed

+181
-41
lines changed

5 files changed

+181
-41
lines changed

src/_includes/card.html

+30-29
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,57 @@
1-
<script type="text/dot-template">
1+
<script type="text/tempura">
22
{% raw %}
3+
{{#expect it}}
34
<div class="card">
4-
{{? it.classes}}
5-
<span class="{{=it.classes}}" title="{{=it.flashTitle}}"><strong>{{=it.flashText}}</strong></span>
6-
{{??}}
5+
{{#if it.classes}}
6+
<span class="{{{it.classes}}}" title="{{{it.flashTitle}}}"><strong>{{{it.flashText}}}</strong></span>
7+
{{#else}}
78
<span class="spacer"></span>
8-
{{?}}
9+
{{/if}}
910
<header>
10-
<h2 title="{{=it.info.title }}">
11-
{{? it.externalUrl }}
12-
<a href="{{=it.externalUrl }}" target="_blank">{{=it.info.title }}</a>
13-
{{??}}
14-
{{=it.info.title }}
15-
{{?}}
11+
<h2 title="{{{it.info.title}}}">
12+
{{#if it.externalUrl }}
13+
<a href="{{{it.externalUrl}}}" target="_blank">{{it.info.title}}</a>
14+
{{#else}}
15+
{{it.info.title}}
16+
{{/if}}
1617
</h2>
1718
</header>
1819
<section class="api-body">
19-
<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">
20-
<p>{{=it.cardDescription}}</p>
20+
<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">
21+
<p>{{it.cardDescription}}</p>
2122
</section>
2223
<footer>
2324
<h3> OpenAPI: </h3>
24-
<h4>Preferred Version - {{=it.preferred}}</h4>
25+
<h4>Preferred Version - {{it.preferred}}</h4>
2526
<ul class="preferred-api">
26-
<li><a href="{{=it.api.swaggerUrl}}" target="_blank" >JSON</a></li>
27-
<li><a href='{{=it.api.swaggerYamlUrl}}' target="_blank" >YAML</a></li>
28-
<li><a href="{{=it.origUrl}}" target='_blank'>Orig</a></li>
29-
<li><a href='https://redocly.github.io/redoc/?url={{=it.api.swaggerUrl}}' target="_blank" >Docs</a></li>
27+
<li><a href="{{it.api.swaggerUrl}}" target="_blank" >JSON</a></li>
28+
<li><a href='{{it.api.swaggerYamlUrl}}' target="_blank" >YAML</a></li>
29+
<li><a href="{{it.origUrl}}" target='_blank'>Orig</a></li>
30+
<li><a href='https://redocly.github.io/redoc/?url={{it.api.swaggerUrl}}' target="_blank" >Docs</a></li>
3031
</ul>
31-
{{? it.versions }}
32+
{{#if it.versions }}
3233
<details>
3334
<summary><h4>All Versions</h4></summary>
3435
<ul class="other-versions">
35-
{{~it.versions :version:index}}
36+
{{#each versions as version}}
3637
<li>
37-
<span>{{=version.version}}</span>
38+
<span>{{version.version}}</span>
3839
<ul>
39-
<li><a href="{{=version.swaggerUrl}}" target="_blank" >JSON</a></li>
40-
<li><a href="{{=version.swaggerYamlUrl}}" target='_blank'>YAML</a></li>
41-
<li><a href='https://redocly.github.io/redoc/?url={{=version.swaggerUrl}}' target="_blank" >Docs</a></li>
40+
<li><a href="{{version.swaggerUrl}}" target="_blank" >JSON</a></li>
41+
<li><a href="{{version.swaggerYamlUrl}}" target='_blank'>YAML</a></li>
42+
<li><a href='https://redocly.github.io/redoc/?url={{version.swaggerUrl}}' target="_blank" >Docs</a></li>
4243
</ul>
4344
</li>
44-
{{~}}
45+
{{/each}}
4546
</ul>
4647
</details>
47-
{{?}}
48+
{{/if}}
4849
<details>
4950
<summary><h4>Tools</h4></summary>
5051
<ul class="tools">
51-
{{~it.integrations :integration:index}}
52-
<li><a href="{{=integration.template}}" target="_blank" >{{=integration.text}}</a></li>
53-
{{~}}
52+
{{#each it.integrations as integration}}
53+
<li><a href="{{integration.template}}" target="_blank" >{{integration.text}}</a></li>
54+
{{/each}}
5455
</ul>
5556
</details>
5657
</footer>

src/_layouts/apis-page.liquid

-2
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,8 @@
1818
<!-- JS -->
1919
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
2020
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.7.0/marked.js"></script>
21-
<script src="https://cdnjs.cloudflare.com/ajax/libs/dot/1.0.3/doT.min.js"></script>
2221
<script src="https://cdnjs.cloudflare.com/ajax/libs/slideout/0.1.12/slideout.js"></script>
2322
<!-- inject:js -->
24-
<script src="/assets/javascript/apis.js"></script>
2523
<script src="/assets/javascript/main.js"></script>
2624
<!-- endinject -->
2725

src/assets/javascript/apis.js

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict';
22

3+
import * as tempura from "./tempura.js";
4+
35
const dummy = {
46
loading: {
57
preferred: 'Loading...',
@@ -128,16 +130,15 @@ CardModel.prototype.fromAPIs = function(apis) {
128130
return this;
129131
};
130132

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

136137
var updateCards = function(data) {
137138
var fragment = $(document.createDocumentFragment());
138139
$.each(data, function (name, apis) {
139140
var model = new CardModel().fromAPIs(apis);
140-
var view = cardTemplate(model);
141+
var view = cardTemplate({it:model});
141142
fragment.append($(view));
142143
});
143144

@@ -232,5 +233,5 @@ if (window.$) {
232233
$('#search-input').focus();
233234
});
234235

235-
});
236236
}
237+

src/assets/javascript/tempura.js

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
const ESCAPE = /[&"<]/g, CHARS = {
2+
'"': '&quot;',
3+
'&': '&amp;',
4+
'<': '&lt',
5+
};
6+
7+
const ENDLINES = /[\r\n]+$/g;
8+
const CURLY = /{{{?\s*([\s\S]*?)\s*}}}?/g;
9+
const ARGS = /([a-zA-Z$_][^\s=]*)\s*=\s*((["`'])(?:(?=(\\?))\4.)*?\3|{[^}]*}|\[[^\]]*]|\S+)/g;
10+
11+
// $$1 = escape()
12+
// $$2 = extra blocks
13+
// $$3 = template values
14+
function gen(input, options) {
15+
options = options || {};
16+
17+
let char, num, action, tmp;
18+
let last = CURLY.lastIndex = 0;
19+
let wip='', txt='', match, inner;
20+
21+
let extra=options.blocks||{}, stack=[];
22+
let initials = new Set(options.props||[]);
23+
24+
function close() {
25+
if (wip.length > 0) {
26+
txt += (txt ? 'x+=' : '=') + '`' + wip + '`;';
27+
} else if (txt.length === 0) {
28+
txt = '="";'
29+
}
30+
wip = '';
31+
}
32+
33+
while (match = CURLY.exec(input)) {
34+
wip += input.substring(last, match.index).replace(ENDLINES, '');
35+
last = match.index + match[0].length;
36+
37+
inner = match[1].trim();
38+
char = inner.charAt(0);
39+
40+
if (char === '!') {
41+
// comment, continue
42+
} else if (char === '#') {
43+
close();
44+
[, action, inner] = /^#\s*(\w[\w\d]+)\s*([^]*)/.exec(inner);
45+
46+
if (action === 'expect') {
47+
inner.split(/[\n\r\s\t]*,[\n\r\s\t]*/g).forEach(key => {
48+
initials.add(key);
49+
});
50+
} else if (action === 'var') {
51+
num = inner.indexOf('=');
52+
tmp = inner.substring(0, num++).trim();
53+
inner = inner.substring(num).trim().replace(/[;]$/, '');
54+
txt += `var ${tmp}=${inner};`;
55+
} else if (action === 'each') {
56+
num = inner.indexOf(' as ');
57+
stack.push(action);
58+
if (!~num) {
59+
txt += `for(var i=0,$$a=${inner};i<$$a.length;i++){`;
60+
} else {
61+
tmp = inner.substring(0, num).trim();
62+
inner = inner.substring(num + 4).trim();
63+
let [item, idx='i'] = inner.replace(/[()\s]/g, '').split(','); // (item, idx?)
64+
txt += `for(var ${idx}=0,${item},$$a=${tmp};${idx}<$$a.length;${idx}++){${item}=$$a[${idx}];`;
65+
}
66+
} else if (action === 'if') {
67+
txt += `if(${inner}){`;
68+
stack.push(action);
69+
} else if (action === 'elif') {
70+
txt += `}else if(${inner}){`;
71+
} else if (action === 'else') {
72+
txt += `}else{`;
73+
} else if (action in extra) {
74+
if (inner) {
75+
tmp = [];
76+
// parse arguments, `defer=true` -> `{ defer: true }`
77+
while (match = ARGS.exec(inner)) tmp.push(match[1] + ':' + match[2]);
78+
inner = tmp.length ? '{' + tmp.join() + '}' : '';
79+
}
80+
inner = inner || '{}';
81+
tmp = options.async ? 'await ' : '';
82+
wip += '${' + tmp + '$$2.' + action + '(' + inner + ',$$2)}';
83+
} else {
84+
throw new Error(`Unknown "${action}" block`);
85+
}
86+
} else if (char === '/') {
87+
action = inner.substring(1);
88+
inner = stack.pop();
89+
close();
90+
if (action === inner) txt += '}';
91+
else throw new Error(`Expected to close "${inner}" block; closed "${action}" instead`);
92+
} else if (match[0].charAt(2) === '{') {
93+
wip += '${' + inner + '}'; // {{{ raw }}}
94+
} else {
95+
wip += '${$$1(' + inner + ')}';
96+
}
97+
}
98+
99+
if (stack.length > 0) {
100+
throw new Error(`Unterminated "${stack.pop()}" block`);
101+
}
102+
103+
if (last < input.length) {
104+
wip += input.substring(last).replace(ENDLINES, '');
105+
}
106+
107+
close();
108+
109+
tmp = initials.size ? `{${ [...initials].join() }}=$$3,x` : ' x';
110+
return `var${tmp + txt}return x`;
111+
}
112+
113+
export function esc(value) {
114+
if (typeof value !== 'string') return value;
115+
let last=ESCAPE.lastIndex=0, tmp=0, out='';
116+
while (ESCAPE.test(value)) {
117+
tmp = ESCAPE.lastIndex - 1;
118+
out += value.substring(last, tmp) + CHARS[value[tmp]];
119+
last = tmp + 1;
120+
}
121+
return out + value.substring(last);
122+
}
123+
124+
export function compile(input, options={}) {
125+
return new (options.async ? (async()=>{}).constructor : Function)(
126+
'$$1', '$$2', '$$3', gen(input, options)
127+
).bind(0, options.escape || esc, options.blocks);
128+
}
129+
130+
export function transform(input, options={}) {
131+
return (
132+
options.format === 'cjs'
133+
? 'var $$1=require("tempura").esc;module.exports='
134+
: 'import{esc as $$1}from"tempura";export default '
135+
) + (
136+
options.async ? 'async ' : ''
137+
) + 'function($$3,$$2){'+gen(input, options)+'}';
138+
}

src/index.md

+6-4
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,20 @@ support: true
2828
{% include card.html %}
2929

3030
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
31-
<script>
31+
<script type="module">
32+
33+
import * as apis from "./assets/javascript/apis.js";
34+
3235
$(document).ready(function(){
33-
var newData = false;
34-
if (window.location.href.indexOf('nd=')>=0) newData = true;
3536
$.ajax({
3637
type: "GET",
37-
url: (newData ? "https://raw.githubusercontent.com/APIs-guru/openapi-directory/gh-pages/v2/metrics.json" : "https://api.apis.guru/v2/metrics.json"),
38+
url: "https://api.apis.guru/v2/metrics.json",
3839
dataType: 'json',
3940
cache: true,
4041
success: function (data) {
4142
$('#numAPIs').text(data.numAPIs.toLocaleString());
4243
}
4344
});
45+
apis.loadAPIs();
4446
});
4547
</script>

0 commit comments

Comments
 (0)