- Hey Ethan I am trying to rewrite the FFmpeg FATE website in something faster than Perl CGI.
- You should really look into Node.js.
- I've heard of it, but what the heck is that?
After wrestling with npm and Ubuntu
node mess for an hour, there
it was, a fine workshop with atomic units that are, frankly, extremely
beginner-friendly. Node.js beginner-friendly, not JS beginner-friendly.
After finishing the two workshoppers I just mentioned, I started doing the “expressworks” workshopper. And it was equally amazing.
After about three day, I thought I was proficient enough to at least port the FATE CGI over. And to be honest, I almost was, but not proficient enough to do all the caching stuff, optimization, etc..
I also chose to use EJS because:
- it is what’s used in the first blog post regarding Express.js on Google
- it’s fast
But nevertheless, my friend told me about Jade. So I looked that up. Wow, said me in the past thought, it’s so cool … and clean …
Until I ported my entire project over and benchmarked it. Without caching it was something like 7x slower than EJS and slower than the Perl CGI version. Even with caching it had only ⅔ of the performance of EJS. So, I reverted my 500-line change and cried for 10 minutes, and moved on.
Gaining maintainer status
Interestingly, I started sending PRs fixing broken links on the website of Jade, even when I am not using Jade. After my second very trivial PR to Jade, the wonderful @ForbesLindesay added me to the Jade maintainers team. I was genuinely surprised, as I had literally less than a week of real JS experiences, and now I am on the maintainer team of a module that is installed over 70,000 times per day.
All the easy stuff
Back to the story. I continued contributing to Jade in some documentation stuff and some trivial deprecation stuff. But gradually, I became more familiar with the code base, and more comfortable with working with JS, which frankly is a bastard language, but a bastardly (assuming that’s a word) easy- to-write language.
One of my most recent commits to Jade was adding tests for caching and the CLI, including CLI watching mode where the output is recompiled whenever you modify the source file. The async nature of caching and process spawning were extremely challenging even now as I recall it. I am still fairly proud of myself for being able to figure out a way to do that.
Now and beyond
Now I am the person with the third most commits in Jade, plus I gained a whole lot more experiences with working with a mature Node.js project and @ForbesLindesay. Huge shoutout to everybody who helped me in the process!
I still haven’t gotten to study the Jade parser and lexer, the two beasts TJ wrote a long time ago and are starting to show some age. Forbes said he is going to split them to new repos in the Jade v2 rewrite, so for now let’s wait until a roadmap to v2 is established.
Discovery of EJS v2
While trying to port my app over, I saw that quite a bit was missing from the
new implementation. One of those was the
_with option I depend upon for
enhanced performance. So I opened a PR for that.
Similar to my experiences in Jade, as one of the first contributors to EJS v2, @mde quickly gave me the maintainer permission to EJS as well. And then I started fixing a lot of things in EJS v2, making it almost completely compatible to v1 (else than the filter stuff, which noone uses).
When benchmarking my app, I notice that the performance of it has significantly declined from EJS v1 (by ~70%). So I started optimizing it. In EJS v2.2.1, two changes of mine made the template functions almost 4x faster in some conditions. But still, it was slower than EJS v1 in more complex apps, both in compilation and compiled functions (though not as much as compilation).
At last I pinned the slower performance in compilation to the overuse of regexes and OOP in the new implementation, two issues to which there are no good fixes. Without a second choice, I forked tj/ejs’s repo and started backporting features in EJS v2 to that.
I have to say that my backport worked fairly well. For compilation it is 1.1x
faster than EJS v1 and 3.52x than EJS v2. For template functions, it is almost
1.36x faster than EJS v2 in regular mode and 1.26x faster in
albeit 32% slower than EJS v1 in extreme cases in regular mode.
So I was hoping I could merge my branch to EJS v2, but @mde said (in private):
I’m really not a fan of the dense, acrobatic code in TJ’s repo. If it’s hard for me to figure out, it would also make it hard for people in the community.
So no, we ain’t gonna merge it. Do note that what he said is 100% true. TJ’s
code is, for a lack of a better word, godly, both godly fast and godly
spaghetti. But for me, at least, @mde’s implementation basically splits one
function in TJ’s implementation to something like 7 plus an enum, most of as
simple as heck, but the 3 most important functions no less spaghetti IMO as
TJ’s. True, TJ’s
parse() is long, but that doesn’t mean it’s harder to
read. Rather, it is easier for me because you don’t need to jump back and
forth to look at the three functions doing some very procedural things.
As a result, I officially forked the package, to a very creative name ejs-tj. I will keep maintaining both forks though. For my fork, I intend to keep compatibility with EJS v2 forever (within my lifespan and mde/ejs’s), so I even decided to use the same version numbers as EJS v2’s.
why did I put the last sentence here? Removed