tl;dr - Client-side require with a server-side component that caches
dependencies, bundles them, and caches the bundles. Need feedback on
the concept, syntax. Need suggestions/contributions on implementation.
Although, this works for me, it is almost just a proof-of-concept,
needs work.
As part of a project I'm working on, I spent a few hours writing a
little client-side module loader with a server-side component enabling
what I think is a pretty neat meaning to CommonJS module syntax. This
morning I pulled it out of the rest of my project and attempted to
package it in a useful way for others to use.
The basic idea is this- in your client-side code, you can use require
in either a "synchronous" or asynchronous fashion-
module1 = require('some/path.js');
require('some/other/path.js', function(err,result){module2 =
result;});
An asynchronous require makes a call to the server component to get
the file in question, but before returning the file, the server parses
it, finds all the synchronous require calls, loads those files as well
and returning the whole thing as a package. That way, when the
original file that was asynchronously loaded is executed and comes to
one of those synchronous require calls, that file is already there,
and the require is actually synchronous.
At this point, maybe this screencast demo will help to clarify how it
works:
http://screencast.com/t/nOU53BRYUAX
Put another way:
If I async require fileA, and fileA has synchronous dependencies on
fileB, and fileC, and an asynchronous dependency on fileD, the server-
side component will return (in a single "bundle") and keep in memory
fileA, fileB, and fileC, not fileD, and it will execute fileA.
The client-side also separates fetching the files and eval'ing them
(the method of getting files is xhr+eval). So, let's say fileA has
require('fileB'); that executes when the file is parsed and executed
on the client, but require('fileC') is inside a function somewhere.
Then fileA will first be eval'ed, then fileB when it comes across
that, and the text of fileC will just be in memory, not eval'ed until
that function is called or some other require to it is called by any
other part of the program.
Another example-
fileA has dependencies fileB, fileC, fileD, fileE, fileF
fileG has dependencies fileC, fileE, fileH
When I call require('fileA', function(err,result){return 'yay';});,
the module loader will load fileA, fileB, fileC, fileD, fileE, and
fileF all in a single bundle.
If I, after that, call require('fileG', function(err,result){return
'yay';});, the module loader will only load fileG and fileH!
Hopefully, that's clear....
The advantages-
Being aware of the difference in synchronous and asynchronous require
in your client-side code make it extremely natural to break all your
client-side code into small reusable chunks- there is no penalty and
you don't have to "optimize" later by deciding what to package
together and what to package separately.
Handling dependencies becomes nothing. You don't have to think about
it.
The server can have a "deployment" mode, where it caches what the
dependencies of a file are and doesn't ever need to parse that file
again.
In "deployment" mode, the server can also cache bundles of multiple
files that are requested together, so when another client requests
that same bundle, it is already in memory.
To sum up:
xhr+eval-when-necessary client-side module loader
both synchronous-ish and asynchronous require in your client side-code
--the synchronous require is actually a command to the server-side
component to bundle
server-side component
--parses for dependencies and bundles them together
--can cache dependency parsing results and whole bundles
So- thoughts? Is this a horrible idea? Are there some gotchas that I'm
missing?
Specific advice needed-
• How to package this in a way that it can be easily used in other
projects? How can I make it integrate seamlessly with existing servers
and make it compatible with different transport mechanisms?
• How to handle path resolution?
• Suggestions for licensing?
• Suggestions for a name- (Mundlejs is a portmanteau of Module and
Bundle- didn't really think long about it)
Things that need to be (properly)implemented:
• server-side "parsing" is just a brittle regexp right now:
(line.match /require\('(.*)'\)/)
• neither type of server-side caching is implemented (pretty easy to
do)
• uniquely identify clients and keep the server away of what modules
they already have, so we can just send the diff of cached modules-
currently, I'm sending the entire list of already cached modules with
every xhr call, so the server doesn't load a dependency twice.
• proper compatibility with module specifications (i.e. CommonJS)-
right now, it's just require and module.exports
Code is available here:
https://github.com/meelash/Mundlejs
To test it:
from Mundlejs/tests/, run
node server.js
visit
http://127.0.0.1:1337/ and open your browser console.