AMD is not the problem, your Web tech stack is
The AMD debate is back, and I have something to say.
First let’s begin with some history. Skip to the AMD section at the bottom for the TL;DR; version.
The <link> and <script> tags
Now we have a new problem — how do we find out the dependencies of all the open-source Web components, linearize the assets into one big file for each kind, and pass them to the minifiers without doing too much work?
Enter asset management solutions.
Existing Solutions and their problems
Sprockets & Friends
CommonJS & Node.js
var sanity = require(‘sanity’);
export.happiness = “npm is awesome”;
export.doubleHappiness = “I can export more than 1 thing too!”;
require looks and works like the require/import statements from other languages you are used to. exports is a little weird, but no weirder than the static and extern keywords in C.
This should lead straight to a straight-forward implementation of a sensible asset management system completely decoupled from any server-side frameworks right? Not so fast.
Client-Side Web Components
AMD & require.js
I’m just going to equate the two here because for all intents and purposes, require.js is the de-facto reference implementation of AMD.
Require.js is by far the most popular attempt to bring some order to the world of client-side asset management, but based on my couple of trials with it, I think it suffers a number of serious flaws which I think will render it a passing fad fairly soon. (Do correct me if I’m wrong because I’ve only tried a couple of times and I still feel I’m not totally comfortable with it)
- Too many ways to define or require a module. Too many patterns that are essentially work-arounds and too many options. Too much boilerplate code.
- Still can’t define a Web component that exports more than one kind of resource. You’d think with the number of options require.js gives you, you’d be able to do this, but it doesn’t.
- Hard to debug. This is the most annoying issue that I keep running into. require.js fails silently when there’s a circular dependency . Errors are often not detected until somewhere deep down the call chain where I reference that file. Often times, I can’t even find out which file couldn’t load or which file was trying to load it based on the error messages.
- Pointless asynchronous loading. This has a tendency to lead people believe that they could just require modules on the spot whenever and wherever, this is just wrong. This leads to callback hell and if you are not careful, lots of choppy incremental rendering on the screen. Making lots of requests also goes against common Web performance best-practices.
- r.js has yet more inscrutable configuration options. (50+ options for essentially a thin layer of asset management code on top of the minifiers, many options are duplicates of require.config).
- r.js doesn’t compile require.js into the output bundle by default. Why must the loader be separate?
- Reinventing the wheel of module definition syntax, but not compatible with CommonJS tools and ES6 syntax. This is the most important reason of why I think require.js will fade out in a few years time because it is significantly more complex than ES6 or CommonJS. Even with the ES6 to AMD transpiler, it’s still only a temporary solution until the libraries and tools catch up with ES6.
There are many more idiosyncratic problems with require.js based on my experiences, but I’m too lazy to list them all here.
The Community and The Way Forward
AMD is an admirable attempt to solve a number of issues plaguing client-side Web development, but it is also true that it remains an admirable attempt at best. Its complexity and deficiencies are actually a reflection of the state of affairs for front-end Web development — everybody has been busy reinventing the wheel, nobody asks what the wheel is suppose to roll on, and nobody can agree to combine their efforts to work on anything.
What this ends up with is of course a number of mutually incompatible, half-solutions with varying community sizes.
Is There a Solution?
Well, in the long term, I think the solution is definitely for the browser vendors to agree to what a Web component constitutes of, and come up with a cross-platform packaging solution that enjoys an overwhelmingly wide acceptance. Ideally, visiting a URL will automatically read a dependency metadata file, download the dependencies with WebRTC peer-to-peer, or load them from the cache. This way multiple websites/apps can share library resources. Essentially, a distributed CDN + NPM on steroid, on the browser.
In the medium term, I think quite certainly that ES6's module syntax needs to be finalized as soon as possible so browser vendors can start shipping it, and developers can solve half of the problem first.
Short Term Band-Aid
As far as I know there is only one tool that is close to a solution to packaging Web components — Component. It not only can manage and build the assets for you, it also has a clear vision and definition of what a Web component should consist of. It also has a number of fairly well done plugins that can manage your LESS and SASS files and more. While I’m still waiting for it to support CSS sprites/icon fonts, and image optimization, they shouldn’t be a serious enough problem to hold you back. If you have a large modern day webapp with lots of static assets, I encourage you to check it out.