JavaScript Modules

Understanding the use of modules in JavaScript.

1.0 What are modules

  • Modules play a crucial role in organizing and structuring code within an application. They allow developers to break down large codebases into smaller, manageable pieces. In this article, we’ll delve into the concept of modules, their evolution, and how JavaScript and TypeScript handle them.

  • It is a technique which keep the code organizes in any programme. Java Script default way of creating object using literals is one of the ways of module pattern.

1.1 Object literals

1var myModule = {
2  // data
3}
  • The above is also a pattern where data can be organized within the curly brackets. This method has some shortcoming from data hiding point of view which is deemed essential in object based or object oriented programming.

1.2 Module Pattern

  • It is a unique style of writing code that makes the code behave as a separate unit known as a module. It is commonly achieved by making use of Immediately Invoked function expression that creates an anonymous function. The code inside the anonymous function lives in a scope that provides privacy and state.
1 (function() {
2    // declare private variables functions
3    return {
4      // declare public variables or functions
5    }
6})();
  • In above section variables declared as private are only accessible inside this function and are not visible outside thus providing data encapsulation let’s look at this example below.
 1 var myModule = (function(){
 2  var guestCounter = 0;
 3  return {
 4   increaseCounter = function () { return guestCounter++;},
 5   restartCounter = function () { guestCounter = 0;}
 6    };
 7  });
 8  // Testing the module
 9  // Using the module upon the guest entry
10  myModule.increseCounter();
11  // When need to restart the counter
12  myModule.restartCounter();
  • The difference between using object literal as opposed to module pattern is that here the local variable guestCounter is hidden from the global scope and tightly encapsulated. The two functions used within this module can not be accessed by the outside world except the variable receiving the return object thus regarded as effectively name-spaced. What it means that the functions defined inside the module can only be accessed by a name-space ( variable receiving the return object) hence a template of a module can be defined in a following way
 1var nameSpace = function () {
 2  // private variable
 3  var privateVariable1 = 0
 4  var privateVariable2 = 1
 5  // private method
 6  var privateMethod = function (anyValue) {
 7    console.log(anyValue)
 8  }
 9  // object that needs to be returned so available to nameSpace
10  return {
11    publicVariable: 'public',
12    publicFunction: function (value) {
13      privateVariable1++
14      privateVariable2--
15      privateMethod(value)
16    }
17  } // end of return
18}
  • From the above pattern it can be seen that avoiding object literal and using module pattern, significant amount of benefits can be achieved which leads to safe programming. The methods which needs to be exposed can either be defined within the return object or can be defined separately as functions and can be referred in the return object as shown below.
 1 var nameSpace = (function (){
 2  //private variable
 3  //private method
 4  //method to Expose can be defined here
 5  var printName = function (){ console.log("Printing Name")}
 6  //Object to return
 7  return {
 8   print:printName, // function defined above
 9   ......
10    }
11  });

1.3 Prototype Design Pattern

  • Below a JavaScript constructor function is created then a method name runis added to this object. This is a how it is done. The property prototype is inherited implicitly when a JavaScript object is created
1 var OrganiteModel = function () {
2  this.shape: "pyramid",
3  this.energyType:"Scalar",
4  this.use:"nonCommercial"
5 };
6 OrganiteModel.prototype.run = function (){
7  // running
8 };
  • In above construction an instance can be created using a constructor function which will retain the state initialized in the constructor sharing the function run with other instances. Adding function to prototype can done in the following manner

Note the outside world can not touch anything except what is exposed to them by returning an object with two properties.

 1var OrganiteModel = function () {
 2  this.shape = 'pyramid'
 3  this.energyType = 'Scalar'
 4  this.use = 'nonCommercial'
 5}
 6
 7// Adding prototype
 8OrganiteModel.prototype = (function () {
 9  var on = function () {
10    console.log('It has been turned on')
11  }
12  var off = function () {
13    console.log('It is off!')
14  }
15  //return
16  return {
17    // returning two properties
18    turnOrganiteOn: on,
19    turnOrganiteOff: off
20  }
21})()
22var myOrganite = new OrganiteModel()
23myOrganite.turnOrganiteOn()
24myOrganite.turnOrganiteOff()
  • The above example also shows how function on and off are protected from outside world.

1.4 Observer Design Pattern

  • In above example no
  • is passed to this module but if passed, the data within this module can be used as their local variables. For example the following code accepts two parameters to be used in this module.
 1 var myApp = {};
 2 ..........
 3 ..........
 4 // other code
 5
 6 // module
 7 (function (app,$) {
 8  // app and $ are available inside the module
 9   .........//some code
10  }(myApp,JQuery));
  • In the above code JQuery is a third party global but myApp is probably the only one global defined at the top level. When a third party global is used in your module you are said to have imported a module. And the third party library, module or a package used in your program is said to have exported their package. In case of JQuery you would either include it in your webpage or you would use in other method to load it in your programme. JQuery defines one global variable $ that is used to get the functionality provided by this library.

  • The global variable above myApp can be declared inside the module too so it can be used with the rest of the code and return to be used by other code in the programme. Returning the object using this pattern is known as exporting module . The receiving variable is said to become the closure that have access to internal state of the object returned. Let’s look this scenario below.

 1 var myCar = ( function () {
 2  var myApp = {};
 3  var itsName = "Farari";
 4  var value = 300,000; // private variable
 5  var getValue : function(){return value;}
 6  //module properties
 7  myApp.name = itsName;
 8  myApp.getName = function() {console.log("myApp name is" + myApp.name);}
 9  //
10  return myApp;
11  }());

2.0 Factory pattern

Module systems in js

  1. CommonJs
  2. AMD
  3. UMD
  4. ES

2.1 NodeJS module Systems

  • NodeJs uses CommonJs
  1. Code resides in separate files
  2. Code in one file is not visible to another file unless it is exported explicitly.
  3. From a simple variable to object can be exported.
  4. Usually a function is exported and used in other files.
  5. Follow the standards require if there is any doubt.
  6. The file name becomes the module name which is loaded using require with the correct path.
  7. It has two key words exports and require. NodeJs has a module.exports property that can assigned a new value.
  8. Example showing a function is exported
1var addThem = function (x, y) {
2  return x + y
3}
4module.exports = addThem

Save this code in file addThem.js and have it available anywhere in node by first using the require method using the correct path.

 1var addThem = require('addThem')
 2or
 3var adding = require('./addThem')
 4or
 5var result = require('addThem')
 6
 7result(10, 20) // 30 or
 8
 9// do it directly at the time of lading.
10require('addThem')(10, 20) //30
  • There are some disadvantages of CommonJs
  1. You can have only one module in a JavaScript file.
  2. If you want to use it in a browser, additional tools are required.
  3. Not suitable in some situation being synchronous.

2.2 ES modules

  • They keyword export is used before any line of code to be exported, it may be a variable, function etc. Give this file any name, it becomes a module and import it in another file.
  • When importing use import { my-file-name} form./my-file-name.js`

2.3 Different way of using export and import module

  1. Named export and Named import
    • Whatever you are exporting from your module should have a name.
    • variable name, class name or a function name. It can be used if you only use a particular property and not the whole file.
1// js/sum.js
2export function sum(a, b) {
3  return a + b
4}
5// there are more exports present
6export function square(a, b) {
7  return a * b
8}
  • And when you want to import only one function in another file
1// main.js
2import {sum} from './sum.js`
3//use sum here

2.Default export and default import

  • You add the default key word before what you like to export.
  • Do no use more than one default export, though you are allowed to have more than one.
  • In this case curly brackets are removed otherwise you will get an error.
  1. Default export as a named export
    • JavaScript allows an alias using the as keyword, but when you export it you use an export clause, you must use as to work otherwise it will throw an error. The clause is given below.
 1// file sum.js
 2function sum ( a, b) {
 3 return (a + b);
 4
 5// named default export
 6export {
 7 sum as default
 8 // other items can be present here.
 9}
10
11// Note in export you can have number of items and can rename them as well.
  1. Rename exports and Name imports
    • JavaScript allows you to rename your export and use the new name to be exported
1// sum.js
2
3function sum(a, b) {
4  return a + b
5}
6export { sum as add }
  • When importing use
1// main file
2import { add } from './sum.js`
3
4console.log ( add(2 + 3));
  1. Namespace import
    • So far a we used a named property to be exported and imported, but you can use all of them using namespace *. This symbol * means everything.
import * as <newName> from ./<file-name.js>

newName.sum(2,8)

2.4 Dynamic imports

  • The above are called static imports, they must be at the top level of the file and you can not use inside a block.
  • In dynamic imports an import() operator can be used to load the code on demand. For more see the articles below.

Typescript and modules

1. Modules in JavaScript Before ES6

Before ECMAScript 6 (ES6), JavaScript lacked a standardized module system. However, developers adopted two popular strategies:

  • CommonJS (CJS): CommonJS was widely used in server-side environments (e.g., Node.js). It allowed developers to define modules using require and module.exports. CommonJS modules were synchronous and loaded dynamically at runtime.

  • Asynchronous Module Definition (AMD): AMD emerged for browser-based applications. It used libraries like RequireJS to load modules asynchronously. AMD modules were suitable for client-side development and supported dynamic loading.

2. Modules in JavaScript with ES6

ES6 introduced native support for modules in JavaScript. The ES6 module system provides a unified syntax for defining and importing/exporting modules. Key features include:

  • import and export Keywords: Developers can use import to bring in functionality from other modules and export to expose functionality from their own modules.

  • Static Analysis: ES6 modules are statically analyzable, allowing tools to optimize and tree-shake unused code during bundling.

3. TypeScript’s Role in Modules

TypeScript shares the same module concept as JavaScript. Here’s how TypeScript handles modules:

  • Unified Syntax: TypeScript’s module system aligns with the ES6 module syntax. Developers can use import and export just like in ES6.

  • Targeting Different Module Loaders: TypeScript allows developers to target various module loaders when emitting JavaScript code. Supported targets include Node.js (CommonJS), require.js (AMD), Universal Module Definition (UMD), SystemJS, and native ES6 modules.

Conclusion

Modules are essential for maintaining code quality, reusability, and scalability. Whether you’re working with JavaScript or TypeScript, understanding modules and their role is crucial for building robust applications.

Remember to explore further resources and experiment with modules in your projects. Happy coding! 🚀


References:

  1. How To Use Modules in TypeScript | DigitalOcean ¹
  2. TypeScript Modules ²
  3. TypeScript: Documentation - Modules - Theory ³
  4. Understanding JavaScript Modules As A TypeScript User - geekAbyte

Source: Conversation with Bing, 14/05/2024 (1) How To Use Modules in TypeScript | DigitalOcean. https://www.digitalocean.com/community/tutorials/how-to-use-modules-in-typescript. (2) TypeScript Modules. https://www.typescripttutorial.net/typescript-tutorial/typescript-modules/. (3) TypeScript: Documentation - Modules - Theory. https://www.typescriptlang.org/docs/handbook/modules/theory.html. (4) Understanding JavaScript Modules As A TypeScript User - geekAbyte. https://www.geekabyte.io/2018/12/understanding-javascript-modules-as.html.

References