I have to add this little prequel: it’s 6:54 AM here, I am just
wrapping up the last paragraph of this post, and I am *exhausted*.
Please, please, please excuse any typos or grammar-o’s or brain-o’s or
Cheetos… mmm… now I’m hungry… and really, really, really sleepy…
anyway, this is extremely long, ~1,500 words, because I had a *lot* to
say on the subject. I realize it may take a chunk out of your time,
but it’s a subject *very* near and dear to my heart as a person who’s
written dozens, if not hundreds, of libraries over the years… so I’d
really, really appreciate it if you’d take the time to read it. (-:
Well, I’ve not really been following the CommonJS project (I’m a
Node.js guy, not really all that interested in the other
implementations of ‘SSJS’).
Unfortunately, it’s come to affect me: Ryah just implemented a rather
large change in the way `require()` works, that very much affects many
things I am working on. I talked with him a bit about it, and he
suggested I come here and hash it out with y’all. I understand his
point of view; he wants Node.js to be library-compatible with other
‘SSJS’ implementations.
So, right off the bat, up until Node.js 0.1.16, I had been writing my
own implementation of an alternative to `require()` that solved the
(at that time) minor problems I was having with `require()`. The code
is unfinished (as 0.1.16 threw a huge wrench in my plans, with the
CommonJS `require()` that was no longer possible to make backwards-
compatible from my implementation), but you can check it out here:
http://github.com/elliottcable/poopy.js/blob/master/lib/acquire.js
So, onwards to the problems I see with the CommonJS implementation of
`require()`.
First off, it makes true prototypal programming impossible. When I
first came to JavaScript, the moment I grokked the beautiful
inheritance system, I was absolutely appalled at how all of the major
JS libraries seemed to do their absolute best to push it away, hide
it, ignore it. Most of them seem to try to implement some half-baked
‘classes’ system on top of it, with the intention of replacing it
entirely.
I’ve been working on a ‘framework’ (it’s really much less code, and
much more ideology) to foster purist prototypal development in
JavaScript. I use it exclusively, myself. Unfortunately, you have to
create objects *yourself* (for obvious reasons) to have control over
their prototypal inheritance; the CommonJS `require()` implemented in
Node.js creates this `exports` object for you, leaving you with no way
to control it; you have to use what you’re handed, making prototypal
programming (or really, anything very powerful, even non-prototypally)
improbably difficult or even impossible.
Next, and second on my list of complaints, the `require()` function
returns the `exports` module object. This problem is entirely linked
into the one above, but I expect it to return what the `require()`’d
file `return`s. Again, same problem; I don’t want the system telling
me what my file returns, *I* want to tell the *system* what *I* want
to return, with `return`.
Third, `require()` is not inline-equivalent to the source code of the
file you’re requiring. A huge, huge requirement for many JavaScript
libraries to come in the near future, with JavaScript-as-a-system
taking off, is the ability to take each `acquire()` (the name of my
alternative to `require()`, possibly a bit *too* close, I guess) in a
block of code, remove the string, and replace it wholesale with the
*content* read from the file that the `acquire()`, when evaluated,
would run.
This means that, if I write a module in `subModule.js` as follows:
(function(){
var subModule = parentModule.beget();
subModule['something'] = "Yay!";
return subModule;
})();
… and then require it into another file, `myLibrary.js`:
(function(){
var myLibrary = {};
myLibrary['subModule'] = acquire('subModule.js');
return myLibrary;
})();
… I can then run a ‘client compiler’ that collapses the entire library
into one file, and then minifies/gzips it for the client:
(function(){
var myLibrary = {};
myLibrary['subModule'] = (function(){
var subModule = parentModule.beget();
subModule['something'] = "Yay!";
return subModule;
})();
return myLibrary;
})();
This functionality is absolutely essential for any web-application
libraries which use to be useful in both your client-side and server-
side code; I myself was intending to eventually write a web
development framework that unified the client- and server-side
development environments into one beautiful whole. The inline
semanticity of `acquire()` is what would make that possible.
Fourth, the asynchronicity of `require()` disappeared with 0.1.16.
This is *extremely* out of sorts with Node.js; for those of you not
familiar with Node, even freaking *printing to stdout* is asyncronous.
Requiring external code from the filesystem should *certainly* be
asynchronous.
I am suspicious that this change was also for compatibility with
CommonJS; after all, less purist-async systems than Node.js most
likely directly return a value from `require()` instead of returning a
`Promise`, as I expect Node to… I don’t have a solution to this,
because Node-specific code would really be a lot more consistent if
`require()` were async, and you had to `require().wait()` as with any
other `Promise` to throw it in a coroutine and pass on a continuation…
but then that code wouldn’t work with libraries that had lines like
`var myThing = require('myThing.js');`, and as I gather, the whole
point of CommonJS is to allow libraries like that to work in Node,
Rhino, and so on, at the same time…
I won’t really be that unhappy if `require()` is left synchronous
(even though that’s out of keeping with the *entire* Node.js API), for
compatibility with CommonJS, if an async alternative is provided,
though. Maybe that’s the least of two evils? )-:
Finally, not related to the semantics, but rather to the
implementation… and not so much a requirement, as something that
annoys me, personally, and I am sure would annoy others…
I come from a Ruby history, and having source code files to my library
in the root folder of my library *really* bothers me. I much prefer to
keep it all under `PROJ/lib/`, with the ‘root file’ (what the user
gets if they `require 'project'`) as `PROJ/lib/my_project.rb`.
The file lookup path I was building for `acquire()` worked something
like this, with full backwards compatibility to the *old* Node.js
`require()` function (not, unfortunately, the new one)`:
For `require('foo.js')` in `myProject/lib/myProject.js`:
- `…/myProject/lib/foo.js`
For `require('/foo.js')` in `myProject/lib/myProject.js`:
- `/foo.js` (yes, this comes first; Node.js puts the root directory
*last* in the lookup path for some reason, I remove it from the array
and universally look there first. I’m of the opinion that if I *say* I
want `/foo/bar/gaz.js`, the very first place looked should be, uh, `/
foo/bar/gaz.js`. duh.)
- `/Users/elliottcable/.node_libraries/foo.js`
- `/usr/local/lib/node/libraries/foo.js`
For `require('foo')` in `myProject/lib/myProject.js`:
- `…/myProject/lib/foo` (as a file, not a directory)
- `…/myProject/lib/foo.js`
- `…/myProject/lib/foo.node` (Node.js’s compiled file extension)
- `…/myProject/lib/foo/foo` (as a file, not a directory)
- `…/myProject/lib/foo/foo.js`
- `…/myProject/lib/foo/foo.node`
For `require('/foo')` in `myProject/lib/myProject.js`:
- `/foo`
- `/foo.js`
- `/foo.node`
- `/Users/elliottcable/.node_libraries/foo`
- `/Users/elliottcable/.node_libraries/foo.js`
- `/Users/elliottcable/.node_libraries/foo.node`
- `/Users/elliottcable/.node_libraries/*/lib/foo`
- `/Users/elliottcable/.node_libraries/*/lib/foo.js`
- `/Users/elliottcable/.node_libraries/*/lib/foo.node`
- `/Users/elliottcable/.node_libraries/*/lib/foo/foo`
- `/Users/elliottcable/.node_libraries/*/lib/foo/foo.js`
- `/Users/elliottcable/.node_libraries/*/lib/foo/foo.node`
- `/usr/local/lib/node/libraries/foo`
- `/usr/local/lib/node/libraries/foo.js`
- `/usr/local/lib/node/libraries/foo.node`
- `/usr/local/lib/node/libraries/*/lib/foo`
- `/usr/local/lib/node/libraries/*/lib/foo.js`
- `/usr/local/lib/node/libraries/*/lib/foo.node`
- `/usr/local/lib/node/libraries/*/lib/foo/foo`
- `/usr/local/lib/node/libraries/*/lib/foo/foo.js`
- `/usr/local/lib/node/libraries/*/lib/foo/foo.node`
The last one looks in quite a few places; note that `/Users/
elliottcable/.node_libraries` and `/usr/local/lib/node/libraries` are
simply the *default* paths in `node.libraryPaths` (I think the name of
that variable changed in 0.1.16, though); that search pattern is
repeated for each of them.
More important things to note here:
- For the absolute-path notation, the *actual* absolute-path is always
looked at first, because users might not know better, and developers
might just want to throw an absolute path in there when debugging
something (to be explicit)
- We always look at the filename given, even if it has no extension;
if somebody distributes a useful library inside their `./binary` with
a shebang, it will likely not have an extension, and is still
conceivably acquire-worthy
- Projects can simply be distributed however the author wants, to
eventually be unzipped as a folder into `/usr/local/lib/node/
libraries` or another libraries path; no complex ‘gem’ generation is
necessary, as with Ruby and Python and such
- All of a projects’ code resides in a sub-directory of the project’s
distribution directory (in the example above, that is `…/lib/`, but I
am open to alternatives, as long as it is standardized)
- Distributions of code may have multiple separate projects or modules
within them, you could easily have `PROJ/lib/foo` and `PROJ/lib/bar`
in the same git repository, distributed in the same `.tar.gz`
The final benefit, which needs a little explanation, is that each
module or sub-module can have an initialization file of its own name,
inside a folder of its own name: this means groupings of code can be
stored in folders, with a file of the same name in the folder that
requires and otherwise manipulates the code in the other files in that
folder.
For instance, `require('/myLibrary')` from an arbitrary location would
locate `<libraryPaths>/<projectFolder>/lib/myLibrary.js`, but it would
also locate `<libraryPaths>/<projectFolder>/lib/myLibrary/
myLibrary.js`, allowing you to wrap *all* `myLibrary`-related-code
into a single `myLibrary` folder, instead of having one file (the
initialization file, `myLibrary.js` itself) in the folder above in the
hierarchy.
In the same way, inside your project’s `myLibrary.js`, you can `require
('subModule')`, and get `…lib/myLibrary/subModule.js` (for a
traditional, Ruby/Perl/Python-esque layout), or `…lib/myLibrary/
subModule/subModule.js` (for a layout intent on keeping everything
related to a subject in a single folder).
I will likely continue to develop my `acquire()` function, and I will
likely use it instead of Node.js’s new `require()`, for all of the
reasons listed above; but I thought it was worth my while to, at the
very least, explain my reasoning, before parting from the crowd. I
write code primarily for myself, but I’d be sad to see my code become
useless to anyone who simply chooses a different SSJS system than
Node.js, for no other reason than the implementation of the `require()
` or `acquire()` function.