Aqui vamos entender do que se tratam módulos, qual sua importância, e a evolução ao decorrer do tempo.
O que é um módulo?
Um módulo é uma parte reutilizável de código que encapsula detalhes de implementação e expõe uma API pública para que possa ser utilizado em outro módulo ou programa. Módulo é um padrão usado de diferentes formas e linguagens de programação.
Podemos escrever código sem módulos, mas há alguns motivos para não fazer isso.
- abstração: delegando funcionalidades para bibliotecas específicas para não precisarmos entender a complexidade da sua implementação
- encapsulamento: tornar um código privado dentro de determinado módulo para proteger sua lógica e implementação.
- reuso: evitar escrever o mesmo código várias vezes
- gerenciar dependências: alterar facilmente as dependências sem precisa alterar o código
Módulos antes do ES6
As versões anteriores à ES6 da especificação ECMAScript não foram pensadas para trabalhar com módulos. A comunidade na época ficava responsável por prover soluções e pensar de forma inteligente para simular padrões de código modular no Javascript.
IIFE - ver IIFE
Revealing Module pattern
Esse padrão é similar ao IIFE, a diferença é que atribuímos o retorno à uma variável. Criando um objeto singleton (padrão que restringe a instância da classe para um único objeto)
Module format
Nada mais é do que a sintaxe utilizada para criar um módulo. Antes da ES6 os mais usados eram:
- CommonJS
- AMD
- UMD
- System.register
CommonJS - CJS
CommonJS é um formato de módulo, desenvolvido pelo engenheiro da Mozilla Kevin Dangoor, em 2009, com o objetivo de suprir a falta de uma prática padrão de modularidade adotada pela comunidade Javascript.
Antes do CommonJS a única forma de se implementar módulos era utilizando IIFE's - ver IIFE
Um exemplo de módulo CJS
// app.js // Dependências var dep1 = require("dep1"); var dep2 = require("dep2"); // Métodos function mycustomCode() {} // Método público exposto (API do módulo) module.exports = mycustomCode;
Essa foi a primeira forma suportada pelo Node para a criação de módulos.
AMD - Asynchronous module definition
O AMD foi lançado pelos desenvolvedores da biblioteca RequireJS, um module loader. É uma API assíncrona desenvolvida para o browser.
define(id?, dependencies?, factory);
A função
define
é responsável por encapsular o conteúdo do módulo.- id: especifica a identificação do módulo que será definido
- dependencies: referencia um array com as dependências necessárias. Pode ser também o id de algum outro módulo
- factory: referencia a função callback que será executada para instanciar o módulo
define(['dep1', 'dep2'], function (dep1, dep2) { // Métodos function foo (){}; // Método público porque é retornado function bar (){}; // Método público porque é retornado function jar (){}; // Método privado porque não é retornado return { foo: foo, bar: bar } });
É comum ver esse tipo de implementação no AngularJs. É muito semelhante ao conceito de IIFE - ver IIFE
UMD - Universal module definition
UMD não é uma biblioteca ou API específica, e sim um padrão de se criar módulos no Javascript que oferece compatibilidade com os diversos module loaders, a exemplo do RequireJS, e também com outras definições, como AMD e CJS.
Basicamente é um arquivo que tenta "adivinhar" em tempo de execução qual é o sistema de módulos usado, dessa forma podemos injetar uma tag
<script>
, ou carregar através de um module loader, ou até mesmo com o Node.(function (root, factory) { if (typeof exports === 'object') { // CommonJS module.exports = factory(require('b')); } else if (typeof define === 'function' && define.amd) { // AMD define(['b'], function (b) { return (root.returnExportsGlobal = factory(b)); }); } else { // Variáveis globais root.returnExportsGlobal = factory(root.b); } }(this, function (b) { // O módulo em si return {}; }));
System.register
Este é considerado um novo formato de módulo que foi concebido para suportar a mesma semântica dos módulos ES6, porém na especificação ES5.
import { p as q } from './dep'; var s = 'local'; export function func() { return q; } export class C { }
Durante a transformação do código, o Babel irá gerar o seguinte retorno
System.register(['./dep'], function($__export, $__moduleContext) { var s, C, q; function func() { return q; } $__export('func', func); return { setters: [ // Sempre que uma dependência altera um export // essa função é chamada para atualizar a instância local // o array setter sempre é o mesmo definido acima function(m) { q = m.p; } ], execute: function() { // Usa a função export para atualizar os exports do módulo s = 'local'; $__export('C', C = $traceurRuntime.createClass(...)); } }; });
ES6 Modules
Esse é o sistema de módulos padrão do Javascript implementado na especificação ES6. Essa especificação foi influenciada pelos outros sistemas da época, como AMD e CJS, para satisfazer as necessidades dos desenvolvedores. Esses módulos por padrão executam em strict mode.
import Foo from "foo"; // Dependência import { sayHello as say } from './lib' // Import alias import * as lib from './lib'; // Importa o módulo inteiro de uma vez e aplica o namespace 'lib' export myFunc () {} // Named export export default {} // Default export
Module loaders
Um module loader interpreta e carrega o módulo escrito em um determinado formato, e isso em tempo de execução.
- Carregamos o module loader no browser
- O module loader recebe qual é o arquivo principal a ser carregado
- O module loader baixa e interpreta o arquivo principal
- O module loader baixa os arquivos conforme necessário
Module bundlers
Um module bundler substitui a necessidade de se ter um module loader, porém, a diferença é que é executado em tempo de build.
- Executamos o module bundler passando o arquivo fonte para gerar um bundle
- Carregamos o bundle no browser
Alguns exemplos de module bundlers: Browserify e Webpack