Skip to content

Commit 9728ae0

Browse files
committed
perf: switch to tempura for templating
1 parent 0d1329d commit 9728ae0

File tree

7 files changed

+186
-48
lines changed

7 files changed

+186
-48
lines changed

.eslintrc.json

+6-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"jquery": true
77
},
88
"parserOptions": {
9-
"ecmaVersion": 2017
9+
"ecmaVersion": 2017,
10+
"sourceType": "module"
1011
},
1112
"extends": "eslint:recommended",
1213
"rules": {
@@ -37,7 +38,7 @@
3738
"callback-return": "off",
3839
"camelcase": "off",
3940
"class-methods-use-this": "error",
40-
"comma-dangle": "error",
41+
"comma-dangle": "warn",
4142
"comma-spacing": "off",
4243
"comma-style": [
4344
"error",
@@ -131,6 +132,7 @@
131132
"no-extra-parens": "off",
132133
"no-floating-decimal": "error",
133134
"no-global-assign": "error",
135+
"no-cond-assign": "warn",
134136
"no-implicit-globals": "error",
135137
"no-implied-eval": "error",
136138
"no-inline-comments": "off",
@@ -151,7 +153,7 @@
151153
"no-multi-spaces": "off",
152154
"no-multi-str": "error",
153155
"no-multiple-empty-lines": "error",
154-
"no-negated-condition": "error",
156+
"no-negated-condition": "warn",
155157
"no-nested-ternary": "off",
156158
"no-new": "error",
157159
"no-new-func": "error",
@@ -252,7 +254,7 @@
252254
"spaced-comment": "off",
253255
"strict": "error",
254256
"symbol-description": "error",
255-
"template-curly-spacing": "error",
257+
"template-curly-spacing": "warn",
256258
"unicode-bom": [
257259
"error",
258260
"never"

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

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
'use strict';
1+
import * as tempura from "./tempura.js";
22

33
const dummy = {
44
loading: {
@@ -128,16 +128,15 @@ CardModel.prototype.fromAPIs = function(apis) {
128128
return this;
129129
};
130130

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);
131+
export function loadAPIs() {
132+
var cardTemplateSrc = document.querySelector('script[type="text/tempura"]').innerText;
133+
var cardTemplate = tempura.compile(cardTemplateSrc);
135134

136135
var updateCards = function(data) {
137136
var fragment = $(document.createDocumentFragment());
138137
$.each(data, function (name, apis) {
139138
var model = new CardModel().fromAPIs(apis);
140-
var view = cardTemplate(model);
139+
var view = cardTemplate({it:model});
141140
fragment.append($(view));
142141
});
143142

@@ -232,5 +231,5 @@ if (window.$) {
232231
$('#search-input').focus();
233232
});
234233

235-
});
236234
}
235+

src/assets/javascript/main.js

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
'use strict';
2-
31
function domReady(cb) {
42
document.addEventListener("DOMContentLoaded", cb, false);
53
}

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)