tag:blogger.com,1999:blog-19002723.post6804113371688182340..comments2023-02-21T00:14:00.325-08:00Comments on Tagneto: CommonJS Module Trade-offsJameshttp://www.blogger.com/profile/12067100302830600925noreply@blogger.comBlogger14125tag:blogger.com,1999:blog-19002723.post-61337661308089680872011-04-16T22:07:11.324-07:002011-04-16T22:07:11.324-07:00Mike Koss: I agree that returning from the definit...Mike Koss: I agree that returning from the definition factory function makes it hard to do some kinds of circular dependencies.<br /><br />The latest version of the AMD proposal and RequireJS allow you to use "exports" by specifying it as a dependency, or by using the simplified CommonJS wrapper, define(function(require, exports, module){});<br /><br />However, I have found exports to be only useful if specifying a circular dependency or using multiple modules to build up/enhance another module. <br /><br />Those are not majority use cases for modules, and I do not believe that minority use cases should make the majority use cases more awkward. But I agree those patterns need to be allowed. Having those minority cases explicitly ask for exports seems like a decent compromise.James Burkehttps://www.blogger.com/profile/00451746837849321739noreply@blogger.comtag:blogger.com,1999:blog-19002723.post-15277199067985678212011-04-15T20:42:06.545-07:002011-04-15T20:42:06.545-07:00I actually think that returning the exports from t...I actually think that returning the exports from the module definition closure has some problems. It mandates that the object returned by a require() function, must be created by the defining module. That implies that load order is not important, and that you cannot have circular references between modules.<br /><br />I prefer a looser coupling between module load order by allowing forward references to modules that have yet to be defined.<br /><br />I've open sourced a sample implementation of a CommonJS - compatible module definition library (namespace.js):<br /><br />https://github.com/mckoss/namespace<br /><br />It's truly tiny enough to be included in any other library, and simple enough to be broadly compatible between implementations.Mike Kosshttps://www.blogger.com/profile/16991627140888922439noreply@blogger.comtag:blogger.com,1999:blog-19002723.post-18909362418925607182010-11-02T10:24:31.806-07:002010-11-02T10:24:31.806-07:00Eric Leads: I understand your concern.
I expect ...Eric Leads: I understand your concern. <br /><br />I expect RequireJS will not get into jQuery, because ideally it should be used to load jQuery. jQuery is a great DOM and Ajax library, but it is just one part of building an application. If it tries to bundle too many things, particularly under the jQuery namespace, I think it will lose its effectiveness and easy appeal.<br /><br />I also try to stay in contact with the jQuery project. One of the changes in jQuery 1.4.3 was the readyWait feature, which was designed so that jQuery plays well with script loaders like RequireJS. RequireJS 0.14.5+ now uses readyWait -- it has a special affordance for jQuery in it.<br /><br />I submitted a ticket to the jQuery team that asked for that type of feature, and fortunately John Resig was already thinking of adding something like it, so it worked out.<br /><br />I also take feedback from jQuery users seriously, and I am really trying to make it easy for them to upgrade to well-scoped modules.<br /><br />When I talked to John Resig earlier this year, and from what he has said before about script loading that he would consider for jQuery, it has been a more constrained loading strategy. That may have changed since earlier this year, but I believe RequireJS gives more packaging options and allows for strong code encapsulation. I also introduced the priority config option based on the scenario that John described wanting to hit for script loading.<br /><br />Of course if the jQuery team decided to bundle RequireJS with jQuery, that would be great. However, I am not expecting it. I am focused on trying to build a great script loader that works great in the browser and can leverage CommonJS code when running on the server.<br /><br />The current Dojo codebase has switched to supporting the module API that RequireJS uses, so I believe it is gaining some traction.<br /><br />So while I hope that RequireJS will be a "safe" choice to make, I can appreciate that it still may be early for some folks to make that choice.<br /><br />I hope that by having the code open source and liberally licensed that no one would be stuck with a bad choice, they could always decide to modify to suit their needs.<br /><br />I also believe the way it builds on the JavaScript Module Pattern and encourages *not* using global variables, that if something else were to come along it would be easy to migrate away since the code is well-encapsulated.<br /><br />It sounds like you worked out a pattern that may be similar, and I definitely appreciate the control that gives you.<br /><br />If you just have questions on code loading strategies for your own implementation, I am happy to discuss them, and if you ever decide to give RequireJS a try, I can help answer questions for that too.James Burkehttps://www.blogger.com/profile/00451746837849321739noreply@blogger.comtag:blogger.com,1999:blog-19002723.post-68606335330273179792010-11-01T11:34:10.991-07:002010-11-01T11:34:10.991-07:00James,
If RequireJS makes it into jQuery core, I&...James,<br /><br />If RequireJS makes it into jQuery core, I'm on board. jQuery is always loaded on every page here. If we have JavaScript, we have jQuery.<br /><br />My primary concern right now is that we're working on a huge codebase currently in production that is used by millions of visitors per month. If we adopt an intrusive module loader like RequireJS, and then jQuery adopts a different module loader, we are going to have a mandate to move to jQuery's standard module loader.<br /><br />What are the chances this is going to land in jQuery officially?<br /><br />That is the million-dollar question.<br /><br />- EricEric Leadshttp://ericleads.com/noreply@blogger.comtag:blogger.com,1999:blog-19002723.post-80746542848626864182010-10-28T11:39:30.610-07:002010-10-28T11:39:30.610-07:00Eric Leads: OK, sounds like you want full control ...Eric Leads: OK, sounds like you want full control of the code so you implemented your own conventions and loader hook (if available). Glad to hear that works for you.James Burkehttps://www.blogger.com/profile/00451746837849321739noreply@blogger.comtag:blogger.com,1999:blog-19002723.post-68593646721469413232010-10-28T07:58:15.137-07:002010-10-28T07:58:15.137-07:00James - RequireJS modules will fail completely if ...James - RequireJS modules will fail completely if define() does not exist. That is an unacceptable trade-off for me at least for several years going forward, because define() is not in common use today, and define() will not exist unless RequireJS or a compatible library has already been loaded.<br /><br />In other words, modules that depend on define() cannot function in a stand-alone capacity. That's not a trade-off I could afford to make, for a variety of reasons. What if we decide to change module definition standards in the future? (Very likely as the specs for modules are still very young...) What if the CDN hosting the module library fails?<br /><br />That single point of failure (either way) can break a whole lot of code.<br /><br />My modules still attempt to make a function call to the core library that powers shared module loading, and the async dependency loader -- but they have the ability to fail gracefully, and still do what they can without the loaded dependencies, if those dependencies are not handled, and they do all that mostly by convention, with very little code.<br /><br />They can also take advantage of a server-side build process to load their dependencies. The modules don't know the difference between modules loaded by the lib, and modules made available by a server-side build process.<br /><br />You can do all that with an anonymous, self-executing function. You can't with define().Eric Leadshttp://ericleads.com/noreply@blogger.comtag:blogger.com,1999:blog-19002723.post-20402625117642922772010-10-27T17:28:32.973-07:002010-10-27T17:28:32.973-07:00Eric Leads: I agree that depending on one vendor c...Eric Leads: I agree that depending on one vendor can be hazardous. I am trying to work with the CommonJS group to work out a spec so that others can implement. Right now Nodules and the new dojo-sie loader both implement the core of the API that RequireJS uses, so hopefully multi-vendor support can grow that way too.<br /><br />Since this post was written, Kris Zyp found a way to get anonymous modules to work, so now the module ID is not encoded in the define() call (used to be require.def()).<br /><br />So I hope the API is moving forward.<br /><br />Seems to me that you need a function entry point to a loader if you want generic, nested module dependencies to be traced dynamically vs. doing a build or server transform to make sure all dependencies are loaded.James Burkehttps://www.blogger.com/profile/00451746837849321739noreply@blogger.comtag:blogger.com,1999:blog-19002723.post-72827951054777430072010-10-27T17:14:23.205-07:002010-10-27T17:14:23.205-07:00Every module loading method is going to have trade...Every module loading method is going to have trade-offs. I agree with you that sacrificing async is absolutely not the right trade-off to make in a browser environment.<br /><br />I thought about using RequireJS for a new client side framework, but I decided against it because modules can't self-describe without first loading the RequireJS library.<br /><br />I know that may not seem like an important requirement to some, but IMO, dependency resolution should not lock you into any particular vendor.<br /><br />For a good example of why not, take a look at the kiwi package manager for Node. For a time, it was the preferred package management system - however, in order to use it, you had to call a kiwi function from within your module.<br /><br />Then a less obtrusive package manager came along (npm), and kiwi faded out of popularity. Now there are a ton of node modules with useless kiwi module loading calls.<br /><br />A similar problem could crop up for those who depend on RequireJS.<br /><br />For now I'm shuttling the dependency list out on a namespace similar to exports (in fact, it will use exports if it is available).<br /><br />I don't just return a value, because my module wrapper function is anonymous with no assignment, so there's nothing to catch the return value.<br /><br />The advantage to that is that the user can then assign your exported module to whatever namespace they like.<br /><br />In other words, our module system meets these requirements:<br /><br />* Self-contained - no need to load any vendor library to function.<br /><br />* User-defined module namespace, similar to CommonJS require.<br /><br />* Uses the standard module-pattern anonymous self-executing function that many JS coders should be familiar with (shared by libraries like jQuery).<br /><br />* Loading our client-side architecture library is optional for the functionality of a module that doesn't have any dependencies.<br /><br />* If our library is loaded, the page can take advantage of any common modules loaded or defined by the core library. It doesn't even have to be aware that they exist, or know whether or not the core lib actually loaded. Useful for common cross browser bug fixes, unobtrusive UI enhancements, analytics, multivariate UI testing, etc...<br /><br />* Modules can define a callback to run after the core lib and all dependencies have been completely evaluated.Eric Leadshttp://ericleads.com/noreply@blogger.comtag:blogger.com,1999:blog-19002723.post-54779269625793807652010-04-30T10:37:11.292-07:002010-04-30T10:37:11.292-07:00Julian: if you are using a function wrapper alread...Julian: if you are using a function wrapper already for the module, it seems like more boilerplate to also carry around an exports function vs just using a return.<br /><br />Either exports needs to be passed in to the function wrapper or it is a global. A global would mandate passing in the module's name as an argument to the global export.<br /><br />Given that you need to name the module, then I prefer that just to be part of the function wrapper call, as I use in <a href="http://requirejs.org/docs/api.html#define" rel="nofollow">require.def</a>.<br /><br />As for module naming, using full URLs along with a name makes the module less portable. Most of the time the module's URL can be derived simply from the module name, and for the rarer cases where you need to map that name to a different path, I prefer to do <a href="http://requirejs.org/docs/api.html#config" rel="nofollow">those mappings at the top of the application</a>, where that knowledge will need to be known anyway.<br /><br />On your async loading comment: it sounds like you use XMLHttpRequest then to fetch your modules and inspect the string source for the require calls? For me, <a href="http://requirejs.org/docs/why.html" rel="nofollow">that is not the most scalable/best performing/easy to debug solution available</a>.<br /><br />I appreciate some of this is personal preference though. Thanks for sharing!James Burkehttps://www.blogger.com/profile/00451746837849321739noreply@blogger.comtag:blogger.com,1999:blog-19002723.post-85764632635850856952010-04-30T03:45:04.159-07:002010-04-30T03:45:04.159-07:00Thank you for the interesting article.
I too pref...Thank you for the interesting article.<br /><br />I too prefer to use functions to hold my modules, as this also makes the module easier to load using a script tag without clogging up the global namespace. <br /><br />I am open minded as to whether one uses "exports" or returns an object from the function, or even uses the module function as a constructor. In the end it seems all the same to me, and seems just a matter of where and when the object is created. In fact I am playing around with using a Function as the "exports" Object, so that I can add additional processing, in the form:-<br /><br />function myExportedFunction()<br />{<br /><br />}<br />exports(myExportedFunction); <br />exports(myExportedFunction, "myAlias"); <br /><br />// elsewhere<br />function exports(f, name)<br />{<br /> if (!name)<br /> {<br /> var name = get f's identifier to save extra typing<br /> }<br /><br /> exports[name] = f;<br /> f.owner = exports;<br />}<br /><br /><br />I can't say I am a fan of the CommonJS module naming system, as it seems to link in a complex way (IMHO) file paths and namespaces/module names. I prefer to maintain separation between (a) where my module is stored, and (b) how it is identified (function identifier), and (c) how it is namespaced. My "require" function therefore accepts both a global identifier for a pre-loaded module function, and a URL which is asynchronously loaded.<br /><br />In terms of asynchronous loading (rarely needed), I run a simple stack, whereby for each module, before loading, I inspect the source code of the module function, extract the requires, push the module on the stack, and then start loading the requires, which in turn may be pushed on the stack if they have requires. As each module comes in, it is popped off the stack and loaded.Julianhttp://www.baconbutty.comnoreply@blogger.comtag:blogger.com,1999:blog-19002723.post-237727777843805092010-04-01T10:03:58.970-07:002010-04-01T10:03:58.970-07:00Chris Barber: For build bundles that include multi...Chris Barber: For build bundles that include multiple require.def calls, I use a require.pause() at the start of the require.def calls then a require.resume() at the end to pause and then resume dependency tracing until all the require.def modules have registered.<br /><br />There are other ways to do it, but I find this was the simplest, just concat the files together and bracket it with pause and resume. It also allows JS files that do not participate in require.def semantics to be included without changing their scope.<br /><br />It is a nice way to mix and match more traditional files with a modular system.<br /><br />I was hoping to put RequireJS into Node, but unfortunately it would be a fairly invasive patch, from what I can tell so far. So not sure it makes sense yet.James Burkehttps://www.blogger.com/profile/00451746837849321739noreply@blogger.comtag:blogger.com,1999:blog-19002723.post-43917226693209235242010-04-01T08:49:06.626-07:002010-04-01T08:49:06.626-07:00I for one think the exports thing is absolutely st...I for one think the exports thing is absolutely stupid. I hate the syntax. It's mandatory that I be able to define more than one module in a specific file.<br /><br />I guess I've been spoiled by Dojo and its module system. I can define as many objects as I want in a single file. I also have been able to access resources globally.<br /><br />But async changes the game. You pretty much need to wrap your object definition in a function that can be called after dependencies have been loaded.<br /><br />I think doing a require.def("foo", ["logger"], function (logger) { return { name: "foo" }; }); is the best solution for the web I've seen so far.<br /><br />To accommodate defining multiple modules per file, I suppose you could just have a bunch of require.def() calls in a single file, but then you'll have to make sure they're in the right order. If foo required bar, bar would have to be defined first so that require doesn't try to load a non-existent bar module.<br /><br />I bet that use case is pretty rare. In dijit, I see instances where they define string class to be invoked. For example, dijit.layout.BorderContainer has an attribute "_splitterClass" that defaults to dijit.layout._Splitter. The _Splitter object is defined after the BorderContainer, but isn't instantiated until a BorderContainer instance is created.<br /><br />I'm alright with that and also makes it so you can override the _Splitter object. Then the BorderContainer would need to do a simple require(this._splitterClass).<br /><br />After playing around with node.js, I'm afraid that JavaScript on the server, which I wanted so bad, is going to suck in the end.<br /><br />In conclusion, requirejs kicks ass and I'm unfortunately less than enthusiastic about the future CommonJS conventions.Chris Barberhttp://www.cb1inc.com/noreply@blogger.comtag:blogger.com,1999:blog-19002723.post-45980674767693924062010-03-31T10:33:41.704-07:002010-03-31T10:33:41.704-07:00Kris Zyp: hopefully I indicated that there are val...Kris Zyp: hopefully I indicated that there are valid use cases for circular dependencies.<br /><br />However, I have come across some, and made some myself, that were just bad design. I think it is always good when hitting a circular dependency to do a critical review of it to make sure it makes sense.<br /><br />The larger issue I was trying to point out was that circular dependency cases are a small subset of module cases, and good circular dependencies being an even smaller subset.<br /><br />Circular dependencies need to be allowed in a module system, but if choosing a trade-off that makes the whole set of module cases more awkward to decrease the probability of an error in a circular dependency case, that is not a trade-off I would make.James Burkehttps://www.blogger.com/profile/00451746837849321739noreply@blogger.comtag:blogger.com,1999:blog-19002723.post-58863092995882252282010-03-31T07:19:01.005-07:002010-03-31T07:19:01.005-07:00Good review, but I am curious, you said: "cir...Good review, but I am curious, you said: "circular dependencies are ... usually a sign of bad design". What does this belief originate from?Kris Zyphttps://www.blogger.com/profile/10627088738984039166noreply@blogger.com