In the early days of JavaScript, scripts were relatively small. In many cases, the complete functionality resides in a single JavaScript file. Over time requirements and solutions grow significantly. Mainly, often-used functionalities are shifted into separate files. With this growth of complexity, the danger of unwanted side effects grows as well, and the need for modularisation of source code gets obvious.

No modules edit

The original JavaScript syntax - which is valid until today - does not know borders between source code written in different files of scripts. Everything is known everywhere, regardless of the file organization. The following example shows that the two functions are known from within HTML and from each other. One can call the other.

<!DOCTYPE html>
<html>
<head>
  <script>
  alert("In script 1");
  function function_1 () {
    "use strict";
    alert("In function_1");
    function_2();
  }
  </script>
  <script>
  alert("In script 2");
  function function_2 () {
    "use strict";
    alert("In function_2");
  }
  </script>
</head>
<body>
  <button onclick="function_1()">Click</button>
</body>
</html>

When the HTML page is loaded, both script parts are read by the browser; hence two alert messages are shown. After clicking the button, function_1 is called, which invokes function_2.

The same behavior occurs when you transfer the scripts to external files and refer them via <script src="./function_1.js"></script>

To avoid the possibility of unwanted side effects from one function to another or from one file to another, diverse forms of modularisations have been developed.

ECMAScript modules (ES modules) edit

Since ECMAScript 2015 (ES6), the standard defines a syntax for modules and their behavior. Most browsers support it natively without any additional library.

Concerning HTML, the syntax changes slightly. The <script> element must be extended by the type attribute <script type="module">. This declares a file or an inline script to be a module. Afterward, its internal classes, functions, variables, ... are no longer visible to other inline scripts, files, or HTML. In the following example, a click on the button results in an error message because the inline script with its function function_1 is treated as a module.

<!DOCTYPE html>
<html>
<head>
  <script type="module">
  alert("In script 1");
  function function_1 () {
    "use strict";
    alert("In function_1");
  }
  </script>
</head>
<body>
  <button onclick="function_1()">Click</button>
</body>
</html>

Due to security reasons, it's complicated to create an example with pure inline scripts. We use an example with an external file. When testing, create this file as shown in the following inline script.

  • When the HTML page is loaded, it shows an alert message (line 7).
  • The external file function_1.js publishes its function function_1 to the public (line 16). All other functionality keeps hidden (in this simple example, there is no other functionality).
  • The function function_1 gets imported to the inline script (line 5).
  • We add the event listener via addEventListener to the button (line 10). The event listener consists of an anonymous function that calls function_1 (in the external file). In line 10, the event listener is only declared; at this moment, it is not called.
  • The "use script" statement gets superfluous because modules always act in strict mode.
<!DOCTYPE html>
<html>
<head>
  <script type="module">
  import {function_1} from "./function_1.js";

  alert("In script 1");

  const btn1 = document.getElementById("btn1");
  btn1.addEventListener("click", () => function_1());

  /* create a file 'function_1.js' with the following content:
  function function_1() {
    alert("In function_1");
  }
  export { function_1 };
  */

  </script>
</head>
<body>
  <button id="btn1">Click</button>
</body>
</html>

In essence, the ES6 module syntax consists of the two statements export and import. export is used in the publishing module to make some of its classes, functions, or variables publically available. import is used in the calling script to get access to those objects.

Nodejs modules (CommonJS) edit

Web servers are not steered by HTML elements. Therefore they use to have a different technique to make the decision about which JS scripts they have to tread as modules and which not.

The popular Web server node.js supports the export/import syntax of ES modules. But its default module system is different. It's called CommonJS.

To tell node.js which syntax you use, an additional line must be added to the project's package.json file.

{
  ..
  "type": "module",
  ..

leads to the ES module syntax. An alternative way is the use of the extension '.mjs' instead of '.js' for file names. A "type": "commonjs" line (respective no definition) leads to the node.js specific syntax CommonJS.

Within CommonJS exports are done with modules.exports (please note the additional 's') and imports with the require statement. An example:

// export in a file 'logger.js'
...
function doLogging() { ... };
module.exports = {doLogging};

// import in a file 'main.js'
const doLogging = require('./logger.js')
...

See also edit