A module is a way of dividing or grouping your code into files or sections. Modules are ideally self contained and decoupled, so that changes can be made in a single module without having to rewrite other modules.
Why Use Modules?Modules have a number of benefits including namespacing, better maintainability, and reusability.
Without modules, it’s very easy to get namespace pollution because all variables outside of top level functions are global and accessible everywhere in the code, even if it’s unrelated. Modules allow namespacing, which creates private variables to reduce or eliminate this pollution.
Modules also allow for decoupling of unrelated code. This means that changes in one file should not require changes to be made in other files, resulting in more maintainable code.
Modules allow you to import and export a single module, so that it can be reused as many times as you would like without duplicating the code. This is great because it means that any required changes will only be needed in one spot, instead of all the potential duplicated places.
However, these methods are still not perfect, as they only reduce the problem instead of solving it. You will have less globals, but all of the top level functions can still be accessed throughout the entire codebase. Namespace pollution and collisions (two modules with the same name, or a single module with two versions still being used) can still occur. With large code bases, dependency management also becomes difficult as dependencies have to be loaded in the browser before the code requiring it.
With CommonJs, you can export only those objects that you want to expose, which can then be required or imported into other modules where they are needed. This makes the code reusable, because you can require the exported modules as many times as needed. With CommonJs you avoid namespace pollution and make dependencies explicit. When loading files into the browser, CommonJs uses a server side synchronous approach. This means that each required module is loaded one at a time, and the browser needs to wait for the entire module to load when needed before moving on. This can be a source of slow loading on websites, but there are some workarounds.
AMD (Asynchronous Module Definition)
To import modules using AMD, you use a “define” statement which takes as parameters a list of modules that are being imported, and a callback function that is executed once the modules have loaded. Using the callback approach allows AMD to take a browser-first asynchronous approach to loading modules, in contrast to CommonJs’ server-first synchronous approach. With AMD you can import objects, functions, constructors, strings and other types whereas CommonJs only supports objects as modules. However, AMD isn’t compatible with io or filesystem, and is missing other server side features that CommonJs has.
UMD (Universal Module Definition)
In cases where you need both synchronous and asynchronous module loading you can use UMD. With UMD you can use either CommonJS or AMD.
BundlingBundling is concatenating all your modules into a single file in the right order for dependencies.
Modules and libraries are all in separate files, which means that each file requires a <script> tag in the HTML. The HTML file is loaded on page load, and separate tags for each file means the module and library files would also have to be loaded one by one. This can be a time consuming process as projects get large. In order to reduce the number of requests for loading all the modules, you can bundle them into one or a few (if there are a lot) files. This is known as the build step. As part of bundling you can also minify the code, which is removing any unnecessary characters such as new lines, white spaces and comments. This also helps speed up bundling and the build process.
As previously mentioned, CommonJs uses synchronous loading which can lead to a time consuming page load. One of the common workarounds for this issue in Browserify. Browserify compiles CommonJs for browsers. First Browserify parses the abstract source tree for each dependency starting at the root file, once it has figured out the dependency order it bundles the modules into a single file. Outputting a file ready to be minified. Now you only need to add one <script> tag into the HTML, dramatically reducing load time for large projects.
AMD requires a module loader such as Curl or RequireJS instead of a bundler. Module loaders dynamically load modules as they are needed. Bundling is less important with AMD since it’s asynchronous and progressively downloads required files instead of loading all the files on page load. In reality overtime, it becomes costly for each user action to have to wait for necessary files to be loaded thus bundling and minifying can also be used for AMD.
Webpack is an agnostic module bundler that works with CommonJs, AMD and ES6. One major advantage it provides is code-splitting, this is when code is split into chunks and the chunks are loaded on demand. For example it may not be efficient to load and bundle code that is only executed under a certain condition, so webpack can chunk and bundle this code separately and only load it when needed.
ES6 works in a different way than the methods described so far. In this process during compilation, exports not being used by other modules can be removed via a method called tree shaking. With the use of tree shaking only code that is imported and can be executed is included in the bundles.
Since there is still no native implementation of how browsers use ES6, making ES6 browser friendly requires a bit of extra work. There are a few different ways to do this, one of which is using transpiler. A transpiler such as Babel or Traceur compiles ES6 code into ES5 (can use CommonJS, AMD or UMD) which can then be put through bundler such as Webpack.
- CommonJS and AMD Co-Existing with Webpack
- Module Bundling