Tuesday, November 15, 2011

Why not AMD?

This started as a private response to some tweets by Jeremy Ashkenas that indicated some concern around optionally registering code as an AMD JavaScript module, but since his comments were in a public space, it makes sense to post my response in a public space.

His concerns as mentioned in the tweets:
  • Isn't canonical in any JS runtime
  • Inefficient by default
  • Incompatible with JS.next
  • Known temporary solution
Twitter is bad for this kind of conversation, and Jeremy is busy with work, but hopefully he can expand on his concerns at some point if my responses below do not address them.

Isn't canonical in any JS runtime

The goal is to create a grassroots effort based on real, multiple implementations that makes it canonical. I believe it is following the way to get something standardized. There are multiple implementations (requirejs, curl, dojo and mootools), and there are multiple higher level toolkits that register with it: jQuery, Dojo and MooTools.

It is also based on real world experience supporting modular code in the browser. It is informed by Dojo's previous modular experience, and jQuery's needs for modular loading. I believe this places it in a better position than how modules in ES harmony are being designed, particularly since it can be opt-in that works with ES3 grammar.

Node adopted a variation of CommonJS modules, but Node's implementers have been very explicit about not picking up anything else from CommonJS, and they support their own non-conformant extensions, like module.exports. Node is a monoculture and very inward focused, and since it has sync IO, it is not concerned with browser needs.

Even if CommonJS were supported well in the browser today, there still needs to be a way to embed multiple CommonJS modules in a file. AMD is a great candidate for that format -- CommonJS never standardized on a "transport format" for this need.

I expand on this a bit more in the Why AMD document.

Inefficient by default

I assume this means that it allows separate modules to be loaded async. FWIW, the current state of ES harmony modules will allow separate file loading for each module. This works better for debugging.

For AMD, you can get the "one script file at the bottom of the page" loading with the 750 byte almond AMD shim and runtime http loading.

I cannot see a better solution to this issue. If you have ideas I would like to hear them.

Incompatible with JS.next

Since JS.next/ES harmony modules do not have an implementation yet, I am not sure what this means. It still needs at least one good implementation to prove out the API. I think AMD can better inform the ES harmony effort since it has real deployment by web-based JS users, the harder environment to get right.

Also, given that harmony modules uses new syntax and by default there is no global access, I think they are making it very hard to allow existing code to opt in to being a harmony module. I have given David Herman feedback to this effect.

I want to help make any harmony module implementation better based on AMD's real deployment. I go into it more in the aforementioned Why AMD page, but being able to set the exported value to a function, and the use of loader plugins to avoid nested, async callbacks for simple resources are of great value in any module system.

Known temporary solution

As you might gather from the above, I do not see it as a temporary solution. It has more legs than CommonJS modules because it works well in browsers and it can be used as the "transport format" for CommonJS modules.

In addition, unless someone can get Microsoft to adopt rapid IE updates, even for old IEs, AMD will be around for a long time. It is a great transpile target for ES harmony module code that wants to run in older browsers. I am prototyping that effort, although I need to update to a real parser instead of regexps. I have a branch of narcissus that has the core parser/lexer/definitions that will run in ES3 browsers as part of that work.

All things considered, I would rather not work on JS module formats. I want to get on to building useful things for end users. But the CommonJS effort was deficient (but a great first effort given their design goals -- AMD uses a bunch of their work), and I do not think ES harmony modules are there yet. Instead of just complaining, I worked with others to work out something better and got implementations and actual, real adoption.

I'm open to something else that has done the above, but I have not seen it yet. Feel free to offer alternatives and feedback though. I just want to get to a good solution, but I'm also tired of waiting for something magical to fix the problem.

14 comments:

Thomas Davis said...

Isn't canonical in any JS runtime
From watching discussion about AMD through GGroups, Github and Twitter I think it has been very successful at creating a grassroots effort. This blog response itself is evidence of progressive standardisation.

Inefficient by default
I don't really know what has become less efficient in my and/or others code but I can comment on things that have become more efficient in my work flow.

- Dynamic Dependency Resolution
During development I no longer have to write out script tags or run a compilation step before I view my page.

- Intelligent Concatenation/Compression
I can still achieve the achieve the same compression but now with extra benefits. I can intelligently compile separate parts of my site together, a concept called build layers. If my master project has a jQuery dependency and a sub project I have included also has a dependency on jQuery, my build step knows to only include jQuery once(This was a big win)


Incompatible with JS.next
Correct me if I'm wrong but JS.next won't be supported by the older browsers? So even if there was an implementation I couldn't use it anyway. I would prefer to see a movement where something like AMD will move forward and merge when the time comes.

Known temporary solution
I haven't been able to find alternatives and I have been absolutely impressed by the self-initiative that the AMD community has.

tbranyen said...

Excellent points James. Personally I'm on the fence about AMD, but if major projects start adopting... it becomes harder and harder to say no.

The largest issues I face when thinking of switching to an AMD based manager is that most of the libraries I use, don't "just work".

Having the libraries I work with every day written to conform to AMD, would make it significantly easier for me to make the switch.

I think the biggest issue at the moment is figuring out the most elegant way of having multiple variable dependencies. Basically being able to load either jQuery, Zepto or Ender without the use of plugins.

James Burke said...

tbranyen: requirejs allows loading today's scripts, particularly if they do not have any dependencies, like jQuery/Zepto/Ender. You will need to grab the global name for the dependency after it loads, but it works.

If you have scripts that depend on those dependencies, but they do not use the define() wrapper to express the dependencies, then you are correct, the order plugin is needed in that case.

Or, in the requirejs case, you can use the priority config to make sure the base jQuery/Zepto/Ender is loaded first before loading the other files.

While I hope the order plugin goes away at some point, it is difficult to upgrade the web all at once. There will be some transition pieces, but I am hopeful the optional AMD registration is not much to ask of libraries.

All that said, if you see other ways to make this easier, give a holler.

For instance, I would like to see a command line tool that can fetch files and do auto-wrapping.

This is not very web scalable -- someone needs to fill out the dependencies for each script, and this should really be done by the library owner, and to limit the necessity of too much tooling it should just be as an AMD call in the file, so that writing out to a global is also avoided -- but it might be helpful as part of a transition period.

Patrick Mueller said...

w/r/t debugging concatenated fles, I've posted some notes at http://pmuellr.blogspot.com/2011/11/debugging-concatenated-javascript-files.html

Bernd Matzner said...

Hello James,

are you aware of an example of almond in use? I'm looking for a solution to modularize a large application that does not require asynchronous loading where all JS libraries are minimized with other means than r.js, so the dependency loading aspect of requireJS/AMD loader is neglibile, but the cleanliness of a well-structured application is desired?. Almond seems to be the solution, but the github readme section on usage merely speaks about r.js.

Thanks,
Bernd

James Burke said...

Patrick Mueller: thanks for sharing the link.

One of the core philosophical differences in something like CommonJS vs AMD is how much reliance there needs to be on tools to do basic development, and how many hidden choices and gotchas there are to implementing them.

While source maps may make things nicer for non-JS languages (whenever they land, providing enough browsers support it), requiring a compile step should not be needed for basic JS development. The web already has a build step, it is shift+reload, to paraphrase Alex Russell.

In addition, different people have different tooling preferences. That would be an extra layer of choice to wade through just to start coding.

To get best performance, a build tool may be needed at some point, but a build tool should not be needed to get started, and some folks are fine with the non-built performance.

Similarly, eval() based debugging assumes single origin JS usage if you want it to work in all browsers. It is getting better now with CORS support but asking a developer to then **correctly** configure CORS is too much overhead for me.

To me, AMD is just cheaper to implement overall, when all the costs in configuration and tooling are considered.

AMD needs to exist for built files anyway, where there are more than one module in a file. Might as well just use it when authoring, fewer levels of abstraction.

Bernd Matzner: almond is used in Gladius, an HTML5 game engine. See the build.js file for its use.

Robin Berjon said...

Have you considered bringing it to the WebApps folks (http://lists.w3.org/Archives/Public/public-webapps/) for standardisation? I think that it would be very useful to build it into the platform directly.

James Burke said...

Robin Berjon: I feel it is more in the space of ECMAScript than a DOM/browser related thing, although the interests do overlap.

ECMAScript is trying to work out a module syntax for their next version, so I would want to give them space to try out a prototype implementation first.

I also feel like the amd-implement group is doing a good job of working out multiple implementations and keeping the APIs rolling along.

It may be good to discuss it on that list to see if any participants have any use cases that are not covered, but then I also do not want to get bogged down in committee navel gazing either.

AMD is moving along fairly well with adoption already, and getting feedback from users in the field who are trying the implementations are likely to give a better signal to noise ratio.

Robin Berjon said...

James Burke: I don't have a strong opinion about where it gets done over the ES/W3C line. It seems like a potential good fit on either side. Either way, both groups coordinate and I'm sure they'd talk lots should it come to this one.

I know that ES is working on a module syntax but so far I haven't seen anything about asynchronous loading — which is dead useful. It would be good to link the two together, otherwise we're back to overlaying hacks.

I'm all for implementation experimentation before standards but I feel that we have enough experience now to move to the next step — but maybe I'm wrong. I also don't think that this would turn into "committee navel gazing" — the WebApps group tends to be very implementation oriented and I'd expect it to welcome something driven by implementation. I think that the primary value would be burning this functionality into the platform so that it gets greater usage and does not require (no pun intended :) additional script setup.

Anyway, if you change your mind, don't hesitate to ping me!

James Burke said...

Robin Berjon: the ES module syntax does take into account browser needs for async loading via the module loader api, and the way scripts are fetched. I'm just not sure yet if the new syntax requirements and design goals for modules with static "compile time" export binding are the way to go.

So I would rather work with the ES group to make sure these issues are worked out vs. starting another similar effort in the another standards group. It would probably create more confrontational swirl than trying to solve the problem.

I also apologize for the navel gazing comment. I could have expressed my concern in a more constructive way. Thanks for more detail on the WebApps group. If you have more info on how that group interfaces with ECMAScript, it would be enlightening for me.

Robin Berjon said...

James: Ah, I remember looking and not seeing async loading (it's quite possible that I missed it) hence my comment. If they're rolling that ball, there's indeed no need to create further confusion by involving more people. I do hope that it is broadly reviewed before moving through the process though.

TC-39 and a variety of W3C groups coordinate through the http://lists.w3.org/Archives/Public/public-script-coord/ mailing list. It's a completely informal list where things like WebIDL are worked on and issues that affect both sides can be brought up by whoever's interested. I think that it's been useful.

And don't worry about the navel gazing comment — I've been working on both the development and the standards sides for about ten years now and don't even notice those any more ;-) I'm hoping that that gap can be bridged but it's hard. There's goodwill but there are also good (if frustrating) reasons why things are the way they are. But that's a whole 'nother kettle of fish.

Anonymous said...

tbranyen: sorry but any intelligent code style can be converted (rewrite, wrap, adapter) to AMD in several seconds or minutes. If not I would doubt the quality of original library code.

sasha said...

It has been mentioned the ability to have error handler. I would add the ability not just trap but recover error. If the resource(JS or text,etc) is not available, it still could be recovered from alternative location. Unfortunately I do not know more or less popular API for such task. Promise/Deferred are given only final notification :(

But implementation could treat error handler in own way allowing to alter module w/ error config path and if it was changed from within handler, try again.

The use cases are simple:
frameworks could be shipped pre-configured to use cloud-based resources(CDN). But behind of firewall it will stop working. Automatic switch to local instance will be a lifesaver.

Another (mine) concern is trust in life support on single CDN. Having one or more backup one will delay unavoidable eventual death. Local copy will be lifesaver in looong run. Not to mention $ saver :)

James Burke said...

sasha: agreed, error recover would be nice. There are complications across browsers doing it well, some discussed here. But I'm hoping we will get something that is useable. I'll be experimenting in requirejs and bringing it back to the amd-implement list.