Javascript modules
I’ve been using javascript modules of different kinds without understanding the terminology and approaches, I wanted to make some notes about it:
We’ve using some sort of modules before with Coffeescript/Angular in Rails projects. Often we just had an object App
defined on the global scope, to which we attached the given module:
which compiles to
With the rails sprockets bundling, the different js files were compiled in the order specified in the manifest. There was not a way to control dependencies or load part of it asynchronously. The compiled coffee files were wrapped in a IIFE to provide isolation.
Our process has been very similar with angular
Module Patterns
From JavaScript Modules: A Beginner’s Guide: The Module pattern is used to mimic the concept of classes (since JavaScript doesn’t natively support classes) so that we can store both public and private methods and variables inside a single object. That allows us to create a public facing API for the methods that we want to expose to the world, while still encapsulating private variables and methods in a closure scope.
There’s very good explanation on the Learning JavaScript Design Patterns from Addy Osmani
This is the classic pattern:
He goes to show a common template:
Variation: Import mixins
This shows how to pass and alias globals (e.g jQuery, Underscore) to our module:
Variation: Exports
Revealing Module pattern.
We define all of our functions and variables in the private scope and return an anonymous object with exposes the ones meant to be public: public.
Globals, CommonJS, AMD, UMD
With the previous patterns, we rely on a global variable that sets the namespace for that given module. This is known as the Globals option. In the last years, there’s been some approaches developed to account for dependency order and avoid namespace collisions, by implementing a convention that avoids going through the global scope.
CommonJS
A CommonJS module looks like this:
module
is an special object that will keep a reference to the exported content provided in myModule.js
in a way in which it can be required be calling require('myModule')
in another file. This works on the server, where those requires will load those files synchronously, this is the Node.js approach. The reference passed to module.exports
doesn’t have to be a function:
AMD = Asynchronous Module Definition
This is the asynchronous variant for the browser. A couple of examples:
The define
method can receive three parameters define(id?, dependencies?, factory);
. The first is optional, it would be the module id, if it’s not present it would be the one that the loader (in this case RequireJS
) is expecting according to his configuration for that given file, for example:
The second argument is the dependencies
, an array of the module (ids) required by the module that is being defined. The dependencies must be resolved prior to the execution of the module factory
function, which is the third parameter, and the resolved values will be passed as arguments to this factory function with an order that matches those dependencies.
UMD (Universal Module Definition)
For projects that require you to support both AMD and CommonJS features, there’s yet another format: Universal Module Definition (UMD). UMD essentially creates a way to use either of the two, while also supporting the global variable definition. As a result, UMD modules are capable of working on both client and server.
Here there are some patterns to achieve it https://github.com/umdjs/umd
Example:
This is an example for the Rmodal library, that supports CommonJS, AMD or globals.
ES6 Modules
With the new ECMAScript edition, we have native support for a new type of modules. An ES6 module is a file containing JS code. That file is automatically in strict mode, and you can use the keywords import
and export
inside it.
In that file everything you declare is local to the module, we use export
to make something public.
You can also rename both your exports and imports
To play nice with CommonJS and AMD modules, ES6 treats them as if they had a default
export:
Module objects: you can get the imports as properties from a module namespace object:
Some more good notes from * Es6 In-Depth Modules:
-
All exported identifiers must be explicitly exported by name in the source code. You can’t programmatically loop through an array and export a bunch of names in a data-driven way.
-
Module objects are frozen. There is no way to hack a new feature into a module object, polyfill style.
-
There is no error recovery for import errors. An app may have hundreds of modules in it, and if anything fails to load or link, nothing runs. You can’t import in a try/catch block. (The upside here is that because the system is so static, webpack can detect those errors for you at compile time.)
Links:
- https://medium.freecodecamp.com/javascript-modules-a-beginner-s-guide-783f7d7a5fcc
- https://addyosmani.com/resources/essentialjsdesignpatterns/book/#modulepatternjavascript
- http://www.leanpanda.com/blog/2015/06/28/amd-requirejs-commonjs-browserify/
- https://medium.freecodecamp.com/javascript-modules-a-beginner-s-guide-783f7d7a5fcc
- https://hacks.mozilla.org/2015/08/es6-in-depth-modules