"Modules are like building blocks that we can create software by combining."
A module is just simply a file that exports its own code.
- Modules gives our code structure and boundaries. One's code will be hard to organize and maintain if they don't use modules and no one wants all the codes mingling with each others.
- Modules also allow developers share code to the world through a package manager like NPM.
In order to understand about the development of JavaScipt's modules, we need to walk through 4 stages: Vanilla, IIFEs, CommonJS and ESM.
When JavaScript was first invented by Brendan Eich in 1995, it was known to have been created in 10 days and the creator himself doesn't think that his programing language would be as popular as today.
“No one thought JavaScript would be used at the wide scale it is. Not just reaching lots of people on the Web, but large application like Gmail… To write large code, you don’t just want this little snippet language that I made for beginners…“ Bredan Eich (Creator of JavaScript)
So modules aren't a thing back then, developers would mostly used inline-scripting and script-tags and no one would think of scalability, maintaining a large code base would be a nightmare because there is no structures and organizations.
Around 2006, a global manifesto was raised by a team of UI developers from YAHOO which was "Global Variable are dangerous". The point they trying to make is that using global variable is a risky practice and all the people back then are engaging in. Global variables can spark conflict between naming if there are many people involving in developing the same program and since everyone can access it, it is not very secure.
Create a huge Global namespace object and parse all the variables in that object.
Example: YAHOO.value.getValue
IFFEs or Immediately invoked function expression was used with closures to keep the private data hidden, basically the ideal is that you only need to care of what the function returns and not what happen inside it.
var Dialogue = function () {
//private variables
var dialogue;
//exposed functions
return {
hello: function () {
dialog = "Hello Dwarves!";
console.log(dialogue);
},
goodbye: function () {
dialog = "Goodbye Dwarves!";
console.log(dialogue);
},
};
};
At this stage we have solve the Global namespace pollution problem but we haven't got to the point that we can swapping modules with one another and copy/paste function happens all the time.
Around 2009, people are hype about the potential of JavaScript, you can now run JavaScript on the server-side. And come with that a proposal was made for JavaScript to have a standard way to include other modules. Node created a module implementations name CommonJS.
- Import: using require() method.
- Export: module object using module.exports.
This was design for server development.
- MODULE._LOAD: Node caches it's modules (Node check for cache files, if it not there it will create an instance and caches it).
- MODULE._COMPILE: Create a require function that speciffic for each modules and generate a wrapper function to scope variables the run the wrapper function.
(function (exports, require, module, __filename, __dirname) {
// Module code actually lives in here
});
//dialogue.js
var getDialog (function (){
console.log("Hello Dwarves!");
})
module.exports = getDialogue;
//main.js
const sayHello = require("./dialogue.js");
At this time, CommonJs is a real noviation, organizing files and maintaining code has become much easier.
ESM or ES modules was supported by all major browsers and was the first built-in module in JavaScript and introduce top-level await functionality for module imports.
A feature accessible within modules is top level await. This implies that the await keyword is functional. It enables modules to function as sizable asynchronous functions, allowing code to be assessed before usage in parent modules without preventing the loading of siblings.
The loading and parsing for ESM modules is indeed asynchronous, but the execution of the code in them is synchronous and serialized based on the order they are imported.
- Import: using import method.
- Export: using export method.
//Export syntax
//dialog.js
export default const dialog = "Hello Dwarves!";
//utils.js
export const getHours = () => //do something;
export const getMinutes = () => //do something;
//Import syntax
import dialog from "./dialog.js";
import * as utility from "./utils.js";
import { getHours, getMinutes } from "./utils.js";
ESM has a specfic module type in the browser which they are processed differently and create a fallback for older browsers using bundle.
<!-- Adding module script -->
<script type="module" src="main.js"></script>
<!-- Fallback for older browsers -->
<script nomodule src="bundle.js"></script>
- Right now ESM was supported on all browsers and the latest version of Node except version below version 12.
- ESM waits to execute any code in a module until all of it's imports have been loaded and parsed, then does the binding/side-effects stuff in the relative order that they happen.
- ESM imports are asynchronous (which also allows for top-level await).
- CJS executes imports as it finds them, blocking until they finish.
- CJS works in Node but does not work in browsers.
Although CommonJS can still be used for server development since synchronous wasn't an issue and it was built by Node, using ESM is still the better way to keep the syntax consistent if you were to develop client-side and server-side with JavaScript.
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
- https://dev.to/adamcoster/commonjs-and-esm-importexport-compatibility-by-simple-example-50pl
- https://www.w3schools.com/js/js_modules.asp
- https://nodejs.org/api/modules.html
- https://www.infoworld.com/article/2653798/javascript-creator-ponders-past--future.html