Intro
I think we can risk to say that from its humble beginnings, JavaScript has become the most popular programming language during the past two decades.
As the old adage goes: your true nature cannot be avoided, just as a tree cannot deny its own roots. Similarly, JavaScript has a few important design decisions inherent in being a browser scripting language, which have some substantial consequences.
The Problem
Let us suppose you want to implement a plugin architecture based solution. You have a PluginManager module which will manage all the registered plugins in the system.
import {pluginA} from './plugins/PluginA'
import {pluginB} from './plugins/PluginB'
class PluginManager {
registerPlugins() {
register(pluginA);
register(pluginB);
}
}
export const pluginManager = new PluginManager();
And somewhere in your index.js or similar top level script, you will do something like the below:
import {pluginManager} from './PluginManager'
pluginManager.registerPlugins();
So what is the big problem here, you're on about?
Well, if you look closer, you will see that the PluginManager has explicit knowledge about the plugins it manages. This contradicts all the software design principles, and among other things it means whenever we want to introduce a new plugin we will need to modify the PluginManager.
OK but how other languages go about this?
In a poor man's implementation you could have a simple file like plugins.txt, where you list all your plugins to be loaded:
com.supercool.plugins.PluginA
com.lesscool.plugins.PluginB
And then you would construct your PluginManager with this piece of configuration and it would be able to load the classes and register them. Without explicit knowledge about any of them.
Yes, you can go much fancier than this, and use some Dependency Injection framework for example.
Back to JS
The reason for this is rather prosaic: the lack of dynamic module loading.
In other words, if it ain't mentioned it ain't bundled (and won't be loadable as it won't be there). So that is why everything has to be so explicit, as otherwise the poor bundler would have no knowledge of the stuff and so it would not be bundled.
This is inherent in how JavaScript was born as a scripting language for the browser. Its resources (script files) are treated the very same way as say a GIF, it has to be referenced and made available in a resource file, available to the browser, otherwise it does not exist.
Conclusion
To me, the lack of dynamic module loading means that certain designs are impossible to implement in JavaScript. Also, it fosters brittle architectures based on strong coupling by having to reference concrete module implementations (module files).
Some of these issues are easy to work around, some are less so. At any rate, this is a serious limitation, which is only now becoming more and more apparent, as we are putting JavaScript to more use as a generic programming language, not just the cowboy language of the Internet.
Top comments (3)
Liked the article, JavaScript as a prototypical inheritance language, does not have classical inheritence as we know or import support or metadata, reflection services as we have in static typed languages.
But you can always add frameworks to implement this. AMD bundlers like RequireJS are trying to load dependencies asynchronously on demand. New frameworks like SystemJS are trying to do just this
I like it, this has been always the case with static linked libraries.
"technologies evolve over time..."
Very interesting article. Some of the issues have had to be worked around when it comes to testing frameworks. At least the current reality of bundled JavaScript from es6 modules allows a saner approach than what we had to do with RequireJS!