!== RequireJS. RequireJS is an implementation of AMD plus a whole ecosystem of usefulness.
I recently had some in-depth discussions with Tom Dale who wrote a pretty popular article against AMD. Tom is a good friend and I respect all of his opinions. I wouldn’t say that he’s since ‘come around’ on the AMD issue, though I may have worn his desire to fight against it. I’ll take what I can get. His business partner and all-around web-tech badboy Yehuda Katz remains less convinced and sent me a few questions that he wanted answered. I won’t add them here verbatim, but I’ll try and touch on a lot of that stuff as well. I think we more or less agree these days (but he by no means necessarily endorses what I’m writing here). I’ll explain.
AMD is not a script loader
This misconception is likely propagated by the fact that RequireJS shows up on all of the script loader shootouts. I have written most of my thoughts on this already in my most popular (read: only) Quora answer: What are the use cases for RequireJS vs Yepnope vs LABjs. I’ll mostly just leave it at this: Script loading is a means in which AMD/RequireJS meets their requirements. It’s neither a focus of the project or an integral part to using AMD in production.
AMD makes async loading possible, not required
I often hear that people don’t have an interest in asynchronous modules. This is perfectly fine. There are tons of integrated systems in full stack web frameworks that do a lot of great things for preprocessing that just don’t require asynchronous script loading to be great.
AMD is also so other people can include your module asynchronously.
So if you have your app split up into modules via some preprocessor system, that’s fine, but if you’d like your code to be accessible to people who don’t run the exact same stack as you: AMD works everywhere.
So write all your code how you like it, and right before you release it on github, consider adding this:
1 2 3 4 5 6
And to take this a step further, I’d encourage the preprocessing systems to just process to AMD.
1 2 3 4 5 6 7
Instead of preprocessing this code to work on the web by just concatenating the globals in the correct order, why not just use AMD? It could easily be translated at preprocess time to:
1 2 3 4
Now you have a module that anyone can pick up (and translate it into their own module format if they’d like).
The problem with using anyone else’s format is that AMD is the only format that is suited well for asynchronous loading. This is not important to one group of people, but it is important to a different group of people, and is a valid concern in web development. In this case, AMD is the inclusive module format.
As for ‘synchronous’ module loading with AMD, the api must use the asynchronous pattern, but if a module is already registered, the result is atomic. Not to mention that it’s perfectly valid to use the
require('x'); syntax when you can be sure a module already exists. The syntax allows for asynchronous loading, but it doesn’t require you to load things that way.
AMD requires preprocessing anyways, right?
Yep, but not until build time. Any sane user of RequireJS uses the amazing r.js build tool before pushing to production. So why not just preprocess on request and output “modules” in the correct order and wrap them in a big IIFE?
It would likely work, but in my opinion there is value in being able to develop directly out of a folder, with only static resources. It’s the reason that so many new developers are using LESS.js – just load the file and go. It doesn’t require you to install a watcher, or set up rails, or even learn how to install an additional whole programming language to your highly varied OS landscape. AMD works without all the stuff. It is a developer experience with immediate gratification.
Many people disagree with the sentiment that our tools should all be able to run on the client side in order to facilitate the idea that our web pages can run out of static directories. I definitely agree that there are tons of uses for preprocessors that run outside of the browser, but I don’t fundamentally agree that modules are part of the ‘just nice to have if you can figure out the watchers’ group of tools. Real modules are going into browsers soon, and I think they should be part of the first class citizen group of tools that we run in our browsers. Not to mention, if you do opt-in to using a pre-processor and you want someone else’s preprocessor to be able to understand your modules, you’ll have to agree on a standard compilation target anyways. What better than AMD?
Much of what James writes about in his posts are about how there are a few ‘meh’ things about AMD, but that it meets nearly every requirement of a module system better than any alternative. He doesn’t often mention AMD as a compilation target, but I think it solves everyone’s problems.
It’s not nearly as ‘complex’ as you think it is.
AMD is implemented by attaching a scriptloader to an object. Load something, store it in the object, if someone else asks for it, pull it back out. Much of the size in RequireJS is for other amazing features and developer tooling. You can switch out RequireJS in production with Almond – it is 857 bytes.
The other notion is that it’s a chore to write. If we ignore the fact that most modern code editors could easily store the boilerplate for you, I would argue that it’s actually less characters than not doing it. Also, (nearly) everyone who says this also complains that it forces them to nest their code an extra level, and then they turn around and immediately wrap all of their code in an IIFE.
1 2 3 4 5 6 7 8 9 10 11
Here’s the same AMD module:
1 2 3 4 5 6 7 8 9
~36% less to type, by my quick and dirty measure (stripping whitespace and counting chars). It can be even less if you don’t have any dependencies and just include a define at the end of your file like we did in a previous example.
Sure there’s a level of nesting, but it’s one that you’ll need to put there regardless of whether you’re using AMD or not. Sure that can be generated at request time, but so can the AMD callback function boilerplate.
Why not just polyfill ES6 Modules?
Well, first off, they’re not entirely baked. David Herman – the author of the original Module proposal – just posted a pretty delicious update for some ideas for a simpler and sweeter module syntax.
Secondly, I think it’s a great idea. As soon as the syntax settles back down I think we could all consider setting up preprocessors to allow for the syntax in the browser. This would require a request-time preprocessor because of the invalid nature of the syntax. So if you were able to run a preprocessor, then I think you should use the ES Harmony Module syntax. I think it should then compile to AMD, obviously, but you shouldn’t ever have to worry about it.
Quick RequireJS plug
I obviously like AMD a decent amount, but I also think RequireJS is a fantastic tool in my stack. The ‘plugin’ architecture is not incredibly well-documented, but it essentially acts as module middleware. My favorite application of this feature involves templates.
I use the pattern in my require-handlebars-plugin to great effect. Rather than require a precompiled template during dev mode, I simply prefix my module name, and the template compilation occurs before the callback is invoked.
1 2 3 4 5
This is also similar to the technique where templates are stored in
type="script/tmpl" script tags and pulled in by their id. This means that if you want to compile your templates at build time, you need to fundamentally alter every location that retrieves a template.
With the hbs template plugin, this is handled for you.
1 2 3 4
During development, this loads the template in as text and precompiles it for you. At build time it automatically outputs a precompiled module that is concatenated into the build. This ability alone is like magic to me and I just wanted to tell everyone how much I like it.
You don’t have to like AMD more than whatever you use. It would be nice if we had a standard, compatible web module syntax though. I think AMD is the best-suited candidate for that role, and that the preprocessors should use AMD as a compilation target, rather than a direct competitor. Obviously it’s still cool if you just use it straight up, but that doesn’t need to happen for it to succeed.