🌐

Javascript Modules

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.
  1. Carregamos o module loader no browser
  1. O module loader recebe qual é o arquivo principal a ser carregado
  1. O module loader baixa e interpreta o arquivo principal
  1. O module loader baixa os arquivos conforme necessário
Alguns exemplos de moduler loaders são RequireJS e SystemJS.

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.
  1. Executamos o module bundler passando o arquivo fonte para gerar um bundle
  1. Carregamos o bundle no browser
Alguns exemplos de module bundlers: Browserify e Webpack

Referências