Here's a more concrete example.
I'm working in a project with 60 files. We have 2 different modes of running it.
Load a concatenated version, 1 large file. (Production)
Load all 60 files (development)
We're using a loader so we just have one script in the webpage
That defaults to mode#1 (loading the one large concatenated file). To run the in mode#2 (separate files) we set some flag. It could be anything. A key in the query string. In this example we just do this
loader.js looks something like this
if (useDebugVersion) {
injectScript("app.js");
injectScript("somelib.js");
injectScript("someotherlib.js");
injectScript("anotherlib.js");
... repeat for 60 files ...
} else {
injectScript("large-concatinated.js");
}
The build script is just an .sh file that looks like this
cat > large-concantinated.js app.js somelib.js someotherlib.js anotherlib.js
etc...
If a new file is added we'll likely be using mode#2 since we're doing development we have to add an injectScript("somenewfile.js")
line to loader.js
Then later for production we also have to add somenewfile.js to our build script. A step we often forget and then get error messages.
By switching to AMD we don't have to edit 2 files. The problem of keeping loader.js and the build script in sync goes away. Using r.js
or webpack
it can just read the code to build large-concantinated.js
It can also deal with dependencies, for example we had 2 files lib1.js and lib2.js loaded like this
injectScript("lib1.js");
injectScript("lib2.js");
lib2 needs lib1. It has code inside that does something like
lib1Api.installPlugin(...);
But as the injected scripts are loaded asynchronously there's no guarantee they'll load in the correct order. These 2 scripts are not AMD scripts but using require.js we can tell it their dependencies
require.config({
paths: {
lib1: './path/to/lib1',
lib2: './path/to/lib2',
},
shim: {
lib1: {
"exports": 'lib1Api',
},
lib2: {
"deps": ["lib1"],
},
}
});
I our module that uses lib1 we do this
define(['lib1'], function(lib1Api) {
lib1Api.doSomething(...);
});
Now require.js will inject the scripts for us and it won't inject lib2 until lib1 has be loaded since we told it lib2 depends on lib1. It also won't start our module that use lib1 until both lib2 and lib1 have loaded.
This makes development nice (no build step, no worrying about loading order) and it makes production nice (no need to update a build script for each script added).
As an added bonus we can use webpack's babel plugin to run babel over the code for older browsers and again we don't have to maintain that build script either.
Note that if Chrome (our browser of choice) started supporting import
for real we'd probably switch to that for development but that wouldn't really change anything. We could still use webpack to make a concatenated file and we could use it run babel over the code for all browsers.
All of this is gained by not using script tags and using AMD