In olden days, the scripts were added inside the markup. But as the projects started growing, maintainability became an issue. So people started to maintain scripts as separate files and it got imported via
<script src='...'/> tags for usage.
Moreover, to share the variables and functions, all of them were pushed to the window(global scope). Often this leads to unexpected overrides and malicious usage(global namespace pollution).
The problems with his approach were:
- the order of script loading was important. If one functionality has a dependency on another one, it should ensure the dependency is already loaded before executing. If not it might fail to execute and start throwing errors.
- sharing variables(states) and functions(behaviour) were difficult. The things to be shared were pushed to the global scope(window), which led to unexpected overrides(namespace collisions), malicious usage and global namespace pollution.
Modules were introduced to solve these concerns.
A file with functionality, which can be reused is called a module. The modules helped to solve the global variable conflicts (scope issues). The variables and functions in a module are always file scoped. With modules, sharing was more explicit. Things to shared have to be
exported and things to be consumed has to be
By default, module scripts
- use strict mode
- they are deferred while loading
- executed only once(in the first import) irrespective of the number of times it is being imported in the whole code (singleton).
With modules, the scripts can be split into multiple files, with less headache. The explicit link between the files was well evident, with import and export statements, and it became easier to combine and recombine them as chunks.
ESModules is the language level module system in JS. This is currently under adoption.
To use ESM in the browser we have to define the type as
module for the script.
<script src='index.js' type='module'/>
Apart from ESModules we have:
- AMD(Asynchronous Module Definition): legacy ones
- CommonJS: module system created for Node.js. If you want to use ESModules inside Node, then the module file must have an extension of
- UMD (Universal Module Definition): kind of universal modules, which supports both AMD and CommonJS styles.
When working with modules, we actually try to create a graph of dependencies and a module instance after parsing the files(creating module records). To do this, we just pass an entry file for the graph construction to start. This is done as,
<script src='main.js' type='module'/>
Three different steps in module loading are,
Construction: find, download, and parse all of the files into module records.
Instantiation: find boxes in memory to place all of the exported values in (but don’t fill them in with values yet). Then make both exports and imports point to those boxes in memory. This process is called linking.
Evaluation: run the code to fill in the boxes with the variables’ actual values.
For ESMs, these steps are asynchronous and can be performed separately.
But in the case of CommonJS, all of them are performed synchronous(w/o any breaks). Though each step need not be async.
It is up to the loader how to load files. And the loader varies between the platforms(browser and Node).