For a very long time, I wanted to try to create a project that would be a real JavaScript Application, namely a fat client, without a backend and my hosting, based on open source and some
BaaS /
DaaS . Besides, I was finally tired of
jsperf.com , of these meaningless two steps, the absence of at least some code editor and normal search and the constant loss of my tests, and the story with the captcha, which does not always work, finally finished me off. I finally took the time to implement the long conceived and kill two birds with one stone, implementing the alternative jsperf.
So, first of all the requirements for the project:
- compact and intuitive interface, without steps and captcha;
- normal editor with code highlighting, not just textarea;
- the preservation of the benchmark, so that later it can be easily found, as well as the deletion (anything can happen);
- the ability to download the benchmark and run it locally or via Node.js;
- the ability to add to the "Favorites";
- other nishtyaki.
And now the most interesting: where to store the source of the tests? And the results?
')
Where to store the source?
If there are regular users of
jsperf among you, they remember the recent history when it was completely unavailable precisely because of the storage of the code and the results of the test run. So the task came down to one thing: how to do it so that nothing is stored in oneself, but to transfer it to some service, and better to the user? The answer suggested itself: the ideal place to store the source code is
GitHub , more precisely,
Gist . Almost every developer has it, and this solves several tasks at once:
- storage;
- normal search (by unique tag);
- favorites;
- bonus: test change history (diff) and fork'i.
GitHub has a wonderful
REST API , this is probably one of the reference implementations, there is also an
API for working with Gist. The question remained for small: how to keep Gist on behalf of the user?
Oauth
For authorization, GitHub offers to use
OAuth , this is easy, but requires a minimum backend. There were several ways to go:
- Find some free hosting or BaaS and deploy one of the open source GitHub solutions there.
- Use the service OAuth.io , which has an acceptable free-plan.
I chose
OAuth.io as a very simple and fast way to get started, moreover, if necessary, you can get rid of the service without
serious consequences . Plus, he has a good analytics, a simple
lib for the work of the API and a
bunch of providers for any service, including GitHub. And the buzz that to get started you don’t even have to go through a tedious registration, just click “Sign in with GitHub” and add the keys to your application.
Github API
The next step is to write a wrapper for working with the GitHub API. And there is a small nuance: I really wanted to once again not pull OAuth.io, so as not to go beyond the limits of the free-plan. As it turned out, GitHub allows accessing unauthorized APIs, but such calls are tightly limited, so the Gist getting method has quite a non-trivial logic:
- We check Runtime cache, if there is data, we give.
- If there is data about the user in
localStorage
, we consider that it is already authorized, call the token getting through OAuth.io and make a request to the API. If the authorization fails, send the request to an unauthorized one and hope that the limits are not yet exhausted. - If
localStorage
nothing in localStorage
, we make the request as unauthorized, in case of an error, we try to log in via OAuth.io and repeat the request already as authorized.
We translate it into code, sprinkle
Promise +
fetch and get the following method:
function findOne(id) { let promise; const url = 'gists/' + id; const _fetch = () => { return fetch(API_ENDPOINT + url).then(res => { if (res.status !== API_STATUS_OK) { throw 'Error: ' + res.status; } return res.json(); }); }; if (_gists[id]) {
Where to store the results?
If I decided on the source quickly, then the results are not so simple. I will not torment, just list the solutions that I knew at the time:
- Parse.com - BaaS, have experience using, convenient JS SDK;
- MongoLab.com - DaaS, a microscopic experience using, requires a JS-bike to work;
- Firebase.com - DaaS +, there was no experience, there is a JavaScript SDK and something else;).
Since the project was experimental, the choice fell on Firebase: besides the JavaScript API, it offered twice as much space as on the same MongoLab, a whole gigabyte.
In fact, there was another option: localStorage / IndexedDB +
WebRTC . The idea was as follows: we store the results of the runs in localStorage and if there is someone else online, then we synchronize the data :).
So, Firebase. To use it to disgrace is simple, the documentation does not lie:
https://www.firebase.com/docs/web/quickstart.html .
This is all the code that I had to write to work with Firebase, but the great thing is that the “host” update event is triggered whenever someone runs benchmarks, and you get online without any F5 or cmd + r, you get update charts.
Design
Oh, that's what I do not know, I do not know how, so everything looks like this:
The interface is as informative as possible, the main important parameters are displayed, the result of the run is displayed to the right of the test code, after the test is completed, the lines are highlighted in color, and graphs are constructed under the code. Setup and Teardown made in the "ears" at the bottom of the screen - the decision is controversial, but suitable for most tasks. As a result, all possible information fits on one screen.
As you can see, unlike jsperf, I have code highlighting, using Ace for this.
Ace is a great tool for integrating a code editor into your application. To use it:
For the test run,
Platform.js and
Benchmark.js are used , I draw graphs using
Google Visualization , so the run results and graphs look exactly the same as on jsperf.
Sharing
One of the features is the sharing of the test, now only Twitter and Facebook are supported.
Twitter
There is nothing special to tell: open a popup with pre-installed text, and then the user himself decides whether to post or not.
function twitter(desc, url, tags) { const max = 130; const top = Math.max(Math.round((SCREEN_HEIGHT / 3) - (twttr.height / 2)), 0); const left = Math.round((SCREEN_WIDTH / 2) - (twttr.width / 2)); const message = desc.substr(0, max - (url.length + tags.length)) + ': ' + url + ' ' + tags; const params = 'left=' + left + ',top=' + top + ',width=' + twttr.width + ',height=' + twttr.height; const extras = ',personalbar=0,toolbar=0,scrollbars=1,resizable=1'; window.open(twttr.url + encodeURIComponent(message), 'twitter', params + extras); }
Facebook
Here it is more interesting, I wanted not just to post a link, but immediately a chart to the tape. Google Visualization has a method for getting
dataURI , while FB has a
Graph API , it remains to be their friends:
Binding over Facebook SDK const facebook = { appId: 'XXXXXXX', publichUrl: 'https://graph.facebook.com/me/photos', init() { return this._promiseInit || (this._promiseInit = new Promise(resolve => { window.fbAsyncInit = () => { const FB = window.FB; FB.init({ appId: this.appId, version: 'v2.5', cookie: true, oauth: true }); resolve(FB); };
To convert dataURI we use
https://github.com/blueimp/JavaScript-Canvas-to-Blob/.
And publish:
function facebookPublish(dataURI, message) { return facebook.login().then(token => { const file = dataURLtoBlob(dataURI); const formData = new FormData(); formData.append('access_token', token); formData.append('source', file); formData.append('message', message); return fetch(facebook.publishUrl, { method: 'post', mode: 'cors', body: formData }); }); }
Future plans
- ES6 support.
- Connect third-party libs for the test.
- Comments to the benchmark (support for Markdown).
- View revisions and forks.
Full list of used libraries and polyphiles
Total
As you can see, at the moment you can assemble a high-quality prototype using ready-made solutions and free platforms, spending only your time on it. But this is not just saving money - it is also saving time on the choice of hosting, installation, setting up the necessary software and further administration. All these problems disappear when you use any BaaS or DaaS - they give you a ready-made solution without a headache. Plus, if the project grows and gets stronger, you can always switch to a suitable paid rate or raise the exact same stack on your hosting.
Project page:
http://jsbench.imtqy.com/
Source code and tasks:
https://github.com/jsbench/jsbench.imtqy.com/