Node.js


Introduction

Some time last semester, I first heard about Node.js from a friend of mine who really likes doing web stuff. Our conversation were something like:

TG:
Hey Ethan I am trying to rewrite the FFmpeg FATE website in something faster than Perl CGI.
EL:
You should really look into Node.js.
TG:
I've heard of it, but what the heck is that?
EL:
It's basically server-side JavaScript.

There, I started on my journey learning JavaScript and Node.js.

Learning

So I just Googled something like “learnign node.js” (with spelling errors), and NodeSchool pops up. Nice yellow-themed website. And then there were some so-called “workshoppers” that are “self-guided … lesson modules”. I already knew JavaScript quite a bit, so I directly picked up on “learnyounode”.

After wrestling with npm and Ubuntu nodejs vs 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.

So I thought, nope, this doesn’t work at all, and went back to NodeSchool for the “javascripting” workshopper. And I have to say, I really enjoyed learning JS while my teacher lectures in my math class.

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..

tl;dr: To learning JavaScript, 3+ things should be enough: some NodeSchool workshoppers, proficient Googling skills, and a learning spirit.

FATE

Not much to say here, except when I tried to add some caching some race conditions occurred. So I used a rwlock. Yes, rwlock, in JavaScript. I don’t even have any experiences for mutexes in C (and that shows my level of C…) and I do now in JS.

I also chose to use EJS because:

  1. it is what’s used in the first blog post regarding Express.js on Google
  2. 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.

Jade

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.

Harder stuff

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.

EJS

Discovery of EJS v2

A few weeks ago, when tracking dependencies of fateserver on David, I saw EJS 2.0.2 was just published. As any good project maintainer would do, I went to the tj/ejs GitHub repo, and saw a huge deprecation notice there, saying that EJS v2 is now maintained in mde/ejs. After tinkering around a little bit, I found out that mde/ejs was basically a reimplementation of TJ’s EJS, originally called “Geddy JavaScript Web development framework”. Interestingly, all (well, most, but now it’s all thanks to me ;) of the EJS v1 APIs were there, but were ported to v2 in the last minute. (Matthew is a really fast programmer).

Contributing

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).

Performance issues

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).

Forking

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 !_with mode, 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.

Conclusion

Node.js is really a world of endless wonders, friendly developers, and unlimited job opportunities. I shall continue in my journey to the mastery of the JavaScript language and the environment of Node.js.

EDIT: why did I put the last sentence here? Removed

EEDIT: copyedits