Introduction
External syntaxes are not JavaScript libraries, but appropriate specifications and conventions for defining modules. External syntax can only be used with the help of module loaders that “breathe life” into it. If the loader module supports the syntax, it means that it contains built-in methods that are previously intended to be used through a certain syntax.
The advantage of using these external syntaxes compared to the native ES5 module pattern is that it does not pollute the global domain with module names and that dependencies manager work is enabled.
- Asynchronous Module Definition (AMD) as its name suggests, supports asynchronous loading of modules, which is convenient for working with modules in the browser.
- CommonJS loads modules synchronously and is therefore most commonly used to work with server-side modules in a node.js environment. Although it is not planned to work in the browser, with the help of the “module bundler” it is possible to adapt CommonJS to work in the browser.
- Universal module definition (UMD) is compatible with both AMD and CommonJS definitions and is used mainly if there is a need to load the same module on the server and in the browser.
Explanation:
CommonJS, AMD, or UMD are not JavaScript libraries. These are standardization organizations, such as ECMA (defines the language specification for JavaScript) or W3C (defines JavaScript web APIs, such as DOM or DOM events). The goal of CommonJS or AMD syntax is to define an API for working with modules.
Asynchronous Module Definition – AMD
The AMD syntax is an agreed set of rules and specifications that indicate how the code for creating a module should look. But its implementation is possible only with the help of the module loader, so in addition to using the syntax itself, it is necessary to load the corresponding module loader/bundler.
AMD syntax
The basis of the AMD syntax is the function define(), which defines the module itself and access to other modules (“dependencies”) via passed parameters.
|
1 |
define(id?, dependencies?, factory); |
When calling the “define()” function, the following things are passed to it through parameters:

- id – name of the module (“string”) without an extension and is optional.
- dependensies – an array (optional parameter) filled with the names of required modules or relative paths to all required modules. The order in the array is important because it defines the loading order.
- factory – a function that instantiates a module or object. If the module has dependencies, they must be passed as parameters to this function. If the function returns a value (function, object…) then that value will be assigned as the value that the module exports.
Example
In this example, two modules are shown, the first module has no dependencies, but it needs to be published publicly in the global domain (exported) because it is needed for the second module.
calculator.js
|
1 2 3 4 5 |
define("calculator", function() { return { sum: function(a, b) { return a + b; } }; }); |
app.js
|
1 2 3 4 5 6 |
define("app", ["calculator"], function(calculator) { console.log(calculator.sum(1, 2)); // => 3 } ); |
Practical application in the application
This is the same example used throughout all the articles related to modular programming, but now adapted to the new syntax. We will implement the AMD syntax for working with modules with the require.js module loader, which we previously installed with the help of the “npm” package manager.
input.js
This module has no need for other modules so the argumentin charge of the module name (or relative path to the module) remains empty:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
define([], function() { // PRIVATE VARIABLE var unesenPodatak = ''; // A FUNCTION THAT ACCEPTS INPUT function setPodatak(noviPodatak) { unesenPodatak = noviPodatak; } // A FUNCTION THAT PROCESSES INPUT function getpodatak() { return unesenPodatak; } // PUBLIC DISCLOSURE OF METHODS return { setPodatak: setPodatak, getpodatak: getpodatak }; }); |
budget.js
This module needs the ‘unos.js’ module, so the relative path to that module has been added to the array. In addition, we passed the inserted module as a function argument.
|
1 2 3 4 5 6 7 8 9 10 11 |
define (['./unos'], function(unos){ function calculateScore() { // METHOD CALL FROM unos.js unos.getPodatak(); // goes here part of the code related to BUDGET } // PUBLIC DISCLOSURE OF METHOD return { calculateScore: calculateScore, }; }); |
app.js
This module needs modules: ‘input.js’ and ‘calculation.js’, so we add their relative paths to the array and pass them to the function.
|
1 2 3 4 5 6 7 8 9 10 11 |
define (['./unos', './proracun'], function(unos, proracun){ // "click handler" FOR DATA ENTRY (calls the function from the module unos.js) document.getElementById('entry').addEventListener('change', function() { unos.setPodatak(document.getElementById('entry').value); }); // "click handler" TO START THE CALCULATION (calls the function from proracun.js) document.getElementById('calculate').addEventListener('click', function() { proracun.calculateScore(); }); }); |
index.html
The file used to collect data from the user and return results remains almost the same as in the previous examples except for the module loading part.
|
1 |
<script data-main="js/app" src="node_modules/requirejs/require.js"></script> |
Now instead of multiple sequentially loaded scripts, we have just one that loads the module loader require.js, and it takes care of loading all the others. The “data-main” attribute defines the initial script of the application, and the “src” attribute defines the path to the place where the loader module is installed.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
<html> <head lang="en"> <meta charset="UTF-8"> <title>Modularna aplikacija</title> </head> <body> <nav class="navbar navbar-default"> <div class="container-fluid"> <div class="navbar-header"> <span class="navbar-brand">MODULARNA APLIKACIJA</span> </div> </div> </nav> <div class="form-horizontal" id="nameform"> <!-- UNOS PODATAKA: --> <div class="form-group"> <label for="entry" class="col-sm-2 control-label">Ulazni podaci</label> <div class="col-sm-2"> <input type="text" class="form-control" id="entry" size="20" placeholder="Unesite podatke" /> </div> </div> <!-- DUGME ZA STARTOVANJE APLIKACIJE (CALCULATION): --> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button class="btn btn-success" id="calculate">Calculation</button> </div> </div> </div> <!-- SEKCIJA ZA RETURN REZULTATA: --> <div class="col-sm-10" id="scores"> <h3>REZULTAT:</h3> <div id="result"> <p>Calculate iznos je: <span></span></p> </div> </div> <!-- SEKCIJA ZA UCITAVANJE MODULA --> <script data-main="js/app" src="node_modules/requirejs/require.js"></script> </body> </html> |
Advantages and disadvantages
AMD syntax solves ES5’s biggest shortcomings in dealing with modules:
- built-in module management within the “module loader” that replaces “manual” loading order determination
- global nampespace is “polluted” with only one name (require) instead of all module names.
The disadvantages of this approach are:
- the list of dependencies in the array must match the list of arguments passed to the function, which can be difficult when there are many dependencies.
- syntax requires everything to be wrapped with a define() function, which forces extra code indentation.
- In the case of using module loader and HTTP 1.1 protocol, loading a lot of small JS files can cause performance problems. This problem can be overcome by using the HTTP/2 protocol or by using a module boundler (eg browserify or webpack) which will gather all js files into one big file..
The
It is considered that with AMD it is better to use “module loader” than “module builder”, because then asynchronous work comes to the fore and shows its full potential. Only the necessary modules are downloaded instead of one that contains all of them, which is the case when using the module bundler.
CommonJS

CommonJS syntax is primarily planned for use on the server, to work in synchronous mode. To work on the server, the logical choice is to use the SystemJS loader module, but if we want to use this syntax in the browser environment, we need to use the bundler module (browserify or webpack) which can adapt this syntax to work in the browser. Unlike require.js where the module body needs to be wrapped with a function, there is no wrapper here because each .js file is considered a single module.
Export module
We can export methods in two ways:
- by assigning each method individually to the “module.export“
object
12module.export.nazivMetodePodKojimSeEksportuje1 = nazivMetodeIzModula1module.export.nazivMetodePodKojimSeEksportuje2 = nazivMetodeIzModula2 - by assigning a literal object with the desired method to the object “module.export”
1234module.export = {nazivMetodePodKojimSeEksportuje1 : nazivMetodeIzModula1,nazivMetodePodKojimSeEksportuje2 : nazivMetodeIzModula2}
Module import
Using methods from other modules is enabled by defining a new variable using the require() function that provides a reference to the given module. Within the require() function, the relative path to the required module is passed through the parameter.
|
1 |
var promenjivaKojaImaReferencuNaDrugiModul = require('./drugiModul'); |
The imported module is just a “COPY” of the exported value. The copy of the “procalcun.js” module inside “main.js” has broken the link with the original. Therefore, even when we increment the “counter” variable with the “increment()” method, the “counter” variable will not change, because the variable we imported is a “disconnected copy” of the “counter” variable. That is the “increment()” method will increment the “counter” variable in the original module, but not in our copied version.
lib/budget.js
|
1 2 3 4 5 6 7 8 |
var counter = 1; function increment() { counter++; } module.exports = { counter: counter, increment: increment, }; |
src/main.js
|
1 2 3 4 |
var proracun = require('../../lib/proracun'); console.log(proracun.counter); // 1 proracun.increment(); console.log(proracun.counter); // 1 |
Practical application in the application
This is the same example used throughout all the articles related to modular programming, but now adapted to the new syntax.
input.js
This module has no need for other modules, but it is necessary to export two methods to make them globally accessible:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// PRIVATE VARIABLE var unesenPodatak = ''; // A FUNCTION THAT ACCEPTS INPUT function setPodatak(noviPodatak) { unesenPodatak = noviPodatak; } // A FUNCTION THAT PROCESSES INPUT function getpodatak() { return unesenPodatak; } module.export = { setPodatak : setPodatak, getPodatak : getPodatak } |
budget.js
This module needs the ‘unos.js’ module, so it is imported, and we have exported a method that should be publicly available.
|
1 2 3 4 5 6 7 8 9 |
var unos = require(./unos); function calculateScore() { // METHOD CALL FROM unos.js unos.getPodatak(); // goes here part of the code related to BUDGET } module.export.calculateScore = calculateScore; |
app.js
This module requires modules: ‘input.js’ and ‘calculation.js’.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
var unos = require(./unos); var proracun = require(./proracun) // "click handler" FOR DATA ENTRY (calls the function from the module unos.js) document.getElementById('entry').addEventListener('change', function() { unos.setPodatak(document.getElementById('entry').value); }); // "click handler" TO START THE CALCULATION (calls the function from proracun.js) document.getElementById('calculate').addEventListener('click', function() { proracun.calculateScore(); }); |
index.js
Depending on whether the application is planned to work on the server or in the browser, we need to choose the appropriate tool for working with modules. The loading of modules also depends on the selected tool. The main difference between the index.html file from the AMD example and this one is in the “MODULE LOADING SECTION”, so to avoid repetition I will show only that part:
a) Case to use Modul bundler (Browserify or Webpack)
The module bundler’s job is to package all the modules into a single file, so it’s enough to just load that file:
|
1 2 |
<!-- SEKCIJA ZA UCITAVANJE MODULA --> <script src="bundle.js"></script> |
b) The case of using Module loader (SystemJS)
If SystemJS is used, it needs to be loaded from the location where it is installed via the npm package manager:
|
1 2 |
<!-- SEKCIJA ZA UCITAVANJE MODULA --> <script src="node_modules/systemjs/dist/system.js"></script> |
In addition to this we need to configure a couple of things through a new inline script:
|
1 2 3 4 5 6 7 8 |
<script> System.config({ meta: { format: 'cjs' } }) System.import('js/app.js') </script> |
In the first part of the previous script, we call the config() method of the System object, and through the meta object we define with which format (syntax) it is used. In our case it is CommonJS, so the abbreviation cjs is written. Then through the import method we define where the initial module is according to the relative path. Only the most basic configuration is shown here, see more about this on the official github page.
Advantages and disadvantages
This approach, like AMD’s, solves the problem of “manual” module management and reduces “pollution” of the global domain, but with an even simpler syntax than AMD.
But not everything is ideal, so this approach also has its drawbacks:
- synchronous operation is not the best for the browser and the use of module bundler is mandatory
- each module must be placed in one file
- unlike AMD, the constructor function is not supported here
- does not support “cyclic dependencies”
- CommonJS has a dynamic module structure, which is defined only at the time of code execution (runtime). So in some cases it is not easy to see what is actually being exported until the code is executed.
