Skip to content

Latest commit

 

History

History
147 lines (105 loc) · 8.54 KB

ModularizacaoIntro.md

File metadata and controls

147 lines (105 loc) · 8.54 KB

#Modularização em JavaScript

Modularização implica na divisão das funcionalidades de um código em partes distintas. Os módulos compõe peças que podem ser adicionadas e removidas quando necessário, vejam: reuso de código.

Os frutos do encapsulamento alcançado com a modularização são a redução da complexidadeseparação de interesses e manutenção descomplicada. Ainda, a definição de cada módulo força o programador a determinar quais os limites e responsabilidades de cada porção do código.

Acredito que estes argumentos já justificam a adoção de um sistema de módulos para seu código. Assumindo que estamos escrevendo código segundo a especificação ECMAScript 5, tudo começa por uma das palavras grifadas no início do texto: encapsulamento.

Encapsulamento em JavaScript

Todo programador que se depara com um código, por menos complexo que seja, precisará entender o conceito de escopo. O escopo de uma variável ou função no JavaScript são as linhas de código em que estas são acessíveis.

Encapsulamento é um dos fundamentos da programação orientada a objetos tradicional. Considerando que não temos classes no JavaScript e se entendermos encapsulamento como uma forma de restringir acesso à informação, concluímos que a definição de escopo é o caminho para alcança-lo.

O JavaScript possui um escopo global, que quando em navegadores é window, e aqueles criados a partir da execução de uma função. A maneira mais fácil de alcançar encapsulamento é utilizando uma função anônima invocada imediatamente após sua definição:

(function () {
var hideFromOutside = true;
})();

Por favor, saiba que este pattern chama-se Immediately-Invoked Function Expression (IIFE) e que os parênteses iniciais servem para que a instrução seja reconhecida como uma expressão.

Módulos

Mencionado já há mais de 10 anos, o mais simples dos padrões de escrita de módulos em JavaScript é o module pattern. O padrão consiste de uma IIFE que retorna um objeto com valores e funções, veja:

var counter = (function () {
var current = 0;
return {
name: "counter",
 next: function () {
return current + 1;
},
isFirst: function () {
return current == 0;
 }
};
})();

Revealing Module Pattern

O module pattern possui muitas variações, uma delas é o revealing module pattern. Neste padrão, todas as funções e valores do módulo são acessíveis no escopo local e apenas referências são retornadas na forma de objeto.

var counter = (function () {
var current = 0;
function next() {
return current + 1;
 }
function isFirst() {
return current == 0;
}
 return {
next: next,
isFirst: isFirst
};
})();

Apesar de mencionado no artigo que define o padrão, o ideal é retornar apenas referências a funções, retornar outros valores pode dar dor de cabeça. Revealing module pattern é bastante interessante pela garantia de acesso descomplicado sem a necessidade de uso do this, por exemplo. A propósito, este conceito será útil para a construção de módulos melhores em outros padrões.

Namespace

Os padrões que vimos até então poluem o escopo global da aplicação com a definição de uma série de variáveis. Uma solução é a criação de um namespace de uso específico para os módulos.

window.App = {
	modules: {}
};
App.modules.counter = (function () {
	/* ... */
})();
App.modules.slider = (function () {
	 /* ... */
})();

Módulos robustos

É natural que módulos dependam uns dos outros. Uma característica importante de um sistema de módulos robusto é a possibilidade de indicar quais são as dependências de um determinado módulo e traçar uma estratégia de carregamento caso esta não esteja disponível.

Asyncronous Module Definition (AMD)

Módulos AMD podem ser requisitados, definidos e utilizados a medida que necessários. Nosso contador, se reescrito em AMD, ficaria da seguinte maneira:

define('counter', function () {
	var current = 0;
	function next() {
		return current + 1;
	}
	function isFirst() {
		return current == 0;
	}
	return {
		next: next,
		isFirst: isFirst
	};
});

Diferente de outros sistemas de módulos, as dependências de cada módulo AMD são indicadas na sua própria definição. Isto significa que as dependências não precisam estar prontas para o uso assim que o módulo seja definido, estas podem ser carregadas assincronamente condicionando a execução do módulo.

O trecho de código a seguir define um módulo com duas dependências:

define(
	[
	'dep1',
	'dep2'
	 ],
	function (dep1, dep2) {
		/* ... */
	}
);

Caso tenha achado estranho, saiba que a definição do módulo geralmente utiliza uma formatação de espaços bastante específica para facilitar a leitura e entendimento das dependências.

Em meio ao trecho de código, caso não tenha notado, não definimos o identificador deste módulo. Os módulos podem (e devem) ser definidos um em cada arquivo e, nestes casos, o nome do arquivo torna-se o identificador.

Requisitando módulos

Toda aplicação terá um trecho principal de código que irá requisitar os módulos e fazer o _bootstrap_da aplicação. O require (sim, a semântica é lógica) não exige identificação e atende ao requisito, veja a seguir:

require( [ 'slider', 'counter', 'inputMask' ], function (slider, counter, inputMask) { /* ... */ } );

Uma boa prática é criar uma interface comum para iniciar o comportamento de cada um dos módulos. Existem diferentes preferências, particularmente utilizo uma função init. Desta forma, o corpo de código do require conteria algo como slider.init() e counter.init().

Carregando os módulos

Módulos AMD podem ser utilizados em qualquer navegador, porém sua definição não é suportada nativamente. O que significa que precisamos de uma biblioteca que provenha as tais funçõesdefine e require. O mais popular loader de AMD é o require.js. Desculpe decepcionar, mas sua configuração não está no escopo deste texto.

Empacotando os módulos

Um dos principais argumentos contra o uso de AMD é a demora para o carregamento de todos os módulos e suas dependências. Apesar de possível, o carregamento assíncrono de cada um dos módulos é sumariamente não recomendado levando em conta os protocolos de rede que utilizamos atualmente.

Existem ferramentas como o r.js que tem a função de empacotar os módulos e entregar um único arquivo para donwload no client-side. O r.js depende de Node.js e introduz uma etapa de análise e concatenação dos arquivos.

Caso já possua um workflow para cuidar do seus assets, concatenar os arquivos e utilizar o almondpode ser uma solução bem mais simples. O único detalhe é que você precisará atribuir um identificador para cada módulo. Mesmo que os módulos estejam definidos cada um em um arquivo, lembre-se que a biblioteca entrará em ação apenas após a concatenação.

Motivação para o uso

Os módulos AMD já são utilizados nos mais famosos projetos escritos em JavaScript, basta acessar os repositórios: jQuery, Flight, Lo-Dash, Mout; e muitos outros.

A definição permite ainda definir plugins para estender o comportamento de carregamento dos módulos e carregar outros conteúdos que não sejam unicamente JavaScript.