A JavaScript Modules Manager That Fits in a Tweet
ES6 does provide modules; but unless you’re using Babel you’ll have to rely on third-party libraries such as RequireJS until all major browsers support them.
I use D3 everyday to visualize data about ego networks and have a small (400-500 SLOC) JavaScript codebase I need to keep organized. In the context I work in I must keep things simple as I won’t always be there to maintain the code I’m writing today.
How simple a modules implementation could possibly be? It should at least be
able to register modules and require a module inside another; much like
Python’s import
. It should also handle issues like circular dependencies
(e.g. foo
requires bar
which requires foo
) and undeclared modules.
Modules should be lazily loaded, i.e. only when they are required; and
requiring twice the same module shouldn’t execute it twice.
Well, here is one:
It’s 136-bytes long. 139 if you count the variable definition. At this level you can’t expect long function names but here is an usage example:
This code will print 42
in the console. It only uses two modules but the
implementation works with an arbitrary number of modules. A module can depend
on any number of other modules that can be declared in an arbitrary order.
Consider this example:
m1
depends on m2
which depends on m3
which itself depends on m1
. The
implementation won’t die in an endless loop leading to a stack overflow but
will fail as soon as it detects the loop:
Admittedly this error message doesn’t give us too information but we have to be
thrifty in order to fit under 140 characters. The prefix p:
tells you the
error comes from p
, and the part after is the faulty module. It can either be
a wrong name (the module doesn’t exist) or a circular dependency.
Walk-through
Note: don’t use this at home. This is just an experiment; I eventually used Browserify for my project.
We need an object to map modules to their functions; we’ll populate it on calls
to register
. We need another object to store the result of their function
call; i.e. what they export. I added a third object to “lock” a module while
it’s executed in order to detect circular dependencies.
We’ll have something like that:
This works and is pretty short; but that won’t fit in a Tweet ;)
Let’s compact the exceptions into one because those strings take a lot of place:
The error is less explicit but we’ll accept that here.
We try to get as little code as possible then use YUI Compressor to remove the spaces and rename the variables. This means we can still work with (mostly) readable code and let YUI Compressor do the rest for us.
I measure the final code size with the following command:
Right now we have 240 bytes. We need a way to remove 100 bytes. Let’s rename
the attributes. _fn
becomes f
; _m
becomes m
, _lock
becomes l
and
the public methods are reduced to their first letter. We can also remove the
var
since p
will be global anyway. Let’s also reduce the error message
prefix to "p:"
.
That’s 186 bytes once compressed. Not bad! Note that we have twice the same
line in the g
function (previously known as “get
”):
We can invert the first if
condition and fit the whole code in it;
combining both return
s into one. This is equivalent to transforming this
code:
Into this one:
The first form is preferable because it removes one indentation level for the
function body. But here return
is a keyword we can’t compress.
Speaking of keyword we can’t compress; how could we remove the delete
? All we
care about is to know if there’s a lock or not, so we can set the value to
false
instead, at the expense of more memory. This saves us only one byte but
since we only care about the boolean values we can replace true
with 1
and false
with 0
.
We’re now at 166 bytes and the g
function looks like this:
Now, what if we tried to remove one of the three objects we’re using? We need to keep the functions and the results in separate objects but we might be able to remove the locks object without losing the functionality.
Assuming that modules only return objects let’s merge m
and l
. We’ll set
p.m[A]
to 0
if it’s locked and will then override the lock with the result.
p.m[A]
then have the following possible values:
undefined
: the key doesn’t exist; the module hasn’t been required yet0
: the module is currently being executed- something else: the module has already been executed; we have its return value
We need to modify our code a little bit for this:
Note that this allowed us to get ride of the try
/finally
which let us go
down to 143 bytes. We can already save two bytes by using < 1
instead of
=== 0
.
Replacing ||
(boolean OR
) with |
(binary OR
) saves one more byte and
allows us to fit in 140 bytes! We can go further and remove the brackets for
the inner if
since it only has one instruction. We need to do that after the
compression because YUI Compressor adds brackets if they’re missing.
The final code looks like this:
That’s 139 bytes once compressed! You can see the result at the top of this
blog post.
Please add a comment below if you think of any way to reduce this
further while preserving all existing features.
Thank you for reading!