Javascript Modular

Javascript Modular

Resolvendo seus problemas

“Todo desenvolvedor web que se preze tem um bom conhecimento de Javascript.”

Imagine que você está desenvolvendo uma aplicação de grande porte, uma rede social por exemplo. Consegue mensurar a quantidade de linhas de JS que teremos?

Você precisa desenvolver uma página que contém diversas funcionalidades complexas, e decide escrever todas as funcionalidades em um único arquivo JS, no fim você tem 900 linhas de código.

Certamente você terá problemas no futuro, você deixou toda a responsabilidade de 900 linhas de JS codada em um arquivo. Daqui um mês vai precisar ser feita uma manutenção, e para uma simples alteração será preciso entender todo o escopo desse código, não concorda?

E o pior, quem desenvolveu esse código foi o cara que saiu da empresa mês passado e agora você precisa se virar.

Shit Happens

Para evitar esses problemas precisamos ter um código bem organizado e modular.

E se ao invés de termos um arquivo com todo esse código, desmembrá-lo em diversos arquivos, cada um com sua devida responsabilidade? No lugar de 1 arquivo com 900 linhas, termos 12 arquivos cada um com cerca de 75 linhas?

Seria muito mais simples de entender e manter essas funcionalidades.

Então, a partir de agora vamos passar a codar de forma organizada, desenvolvedores costumam ter um padrão de código e chamam isso de Design Patterns.

Utilizar um Design Patterns é a solução para trabalhar juntamente com outros desenvolvedores, nos ajuda a manter um código padronizado e organizado, pois todos terão a mesma forma de codificar.

Javascript possui muitos estilos de patterns, vamos abordar apenas alguns, mas se você deseja saber mais existe um artigo muito interessante do Addy Osmani.

Module Patterns

Tabela de conteúdo:

  1. Revealing Module Pattern
  2. AMD
  3. CommonJS
  4. Futuro

Abaixo algumas module patterns que podem ser úteis.

Revealing Module Pattern

// "Module" é meu módulo
// "$, window, document, undefined" são minhas dependências

var Module = (function($, window, document, undefined) {
  'use strict'; // Sempre use *.*

  var privateVar = 'Private',
    publicVar = 'Public';

  function myMethod() {
    alert(privateVar);
  }

  function myOtherMethod() {
    alert(publicVar);
  }

  function setSomethingToPrivateVar(something) {
    privateVar = something;
  }

  // API Publica
  return {
    myCoolMethod: myMethod,
    myOtherMethod: myOtherMethod,
    setSomethingToPrivateVar: setSomethingToPrivateVar,
    // Torna a variável acessível fora do escopo
    publicVar: publicVar
  };

// Executa quando jQuery, window e document estiverem definidos
}(jQuery, window, document));

// Usage
Module.myMethod(); // undefined

Module.myCoolMethod(); // Alerta "Private"
Module.myOtherMethod(); // Alerta "Public"

alert(Module.privateVar); // undefined
alert(Module.publicVar); // Alerta "Public"

// Como mudar a variável privada
Module.setSomethingToPrivateVar('Private Variable');
Module.myCoolMethod(); // Alerta "Private Variable"

Uma vantagem dos Module patterns é que nos dá a liberdade de ter métodos privados que só podem ser consumidos por nosso módulo.

Como eles não estão expostos ao resto da página (apenas nossa API é exportada), eles são considerados verdadeiramente privados.

Asynchronous Module Definition (AMD)

A função do AMD é fornecer uma solução modular que os desenvolvedores podem usar hoje, é uma proposta para que possamos carregar módulos de forma assíncrona.

O AMD trabalha com o conceito de “Definir módulos” e “Requisitar módulos” quando são necessários.

Utilizaremos o RequireJS, que é um module loader e nos permite trabalhar com AMD.

Definindo um módulo

Para definir um módulo é simples, você pode dar um nome para esse módulo (id) e definir suas dependências.

Sendo que nome e dependências não são obrigatórios.

// define(id?, [dependencies?], factory);

define('nome-do-modulo', ['dep1', 'dep2'], function(dep1, dep2) {
  function myFunction() {
    console.log('Esse é meu módulo');
  }

  // Tenho os outros módulos dep1 e dep2 dentro do
  // escopo para exucutar o que for preciso.
  dep1.doSomething();
  dep2.doOtherThing();
});

Requisitando o módulo

Vamos requisitar o mesmo módulo que definimos e executar sua função.

Agora, quando requisitamos um módulo ele passa a ser nossa dependência.

// require([dependencies?], factory);

require(['nome-do-modulo'], function(nomeDoModulo) {
  // Exibirá no console "Esse é meu módulo"
  nomeDoModulo.myFunction();
});

Utilizando em um exemplo mais complexo, com o uso de jQuery.

// Arquivo module.js
// Define um módulo (sem nome) que tem jQuery como dependência
define(['jquery'], function($) {
  'use strict';

  var privateVar = 'Private',
    publicVar = 'Public';

  function myMethod() {
    alert(privateVar);
  }

  function myOtherMethod() {
    alert(publicVar);
  }

  function setSomethingToPrivateVar(something) {
    privateVar = something;
  }

  // API Publica
  return {
    myCoolMethod: myMethod,
    myOtherMethod: myOtherMethod,
    setSomethingToPrivateVar: setSomethingToPrivateVar,
    // Torna a variável acessível fora do escopo
    publicVar: publicVar
  };
});

// Arquivo app.js
// Requisita o módulo "module"
// O require irá procurar por um arquivo chamado module.js
require(['module'], function(module) {
  module.myCoolMethod();
});

Como podemos ver AMD possui uma forma mais clara e limpa de declarar e requisitar módulos. Também é possível carregar scripts on demand.

Ganhamos muito em reusabilidade, pois podemos requisitar o mesmo módulo em outra parte da aplicação.

Perceba também que ter um código modularizado é mais fácil de ser expandido conforme sua aplicação vai evoluindo.

CommonJS

A origem do CommonJS vem através do uso de Javascript em ambientes no lado do servidor, como usamos em NodeJS.

Você vai perceber que é possível requisitar módulos facilmente, de forma semelhante que é feito em outras linguagens como Ruby, Python, Java, etc.

Utilizando esse método, você já ganha de brinde conhecimento para escrever módulos Server-side no NodeJS.

Para saber mais sobre NodeJS veja esse artigo.

Com CommonJS podemos definir módulos nas mesmas premissas do AMD, mas com uma sintáxe ainda mais simples.

As palavras mágicas são exports e require.

Vamos portar o mesmo código escrito em AMD para visualizar como ficaria em CommonJS.

Definindo um módulo

Para definir basta inserirmos o metodo exports junto as declarações de métodos e variáveis que desejamos expor.

// my-function.js

'use strict';

// Requisita as dependências
var dep1 = require('./dep1'),
  dep2 = require('./dep2');

exports.myFunction = function() {
  console.log('Esse é meu módulo');

  dep1.doSomething();
  dep2.doOtherThing();
};

Ou podemos definir todo nosso código e exportar a API na última linha, semelhante ao que fazemos no Revealing Module Pattern com o return.

'use strict';

// Requisita as dependências
var dep1 = require('./dep1'),
  dep2 = require('./dep2');

var myFunction = function() {
  console.log('Esse é meu módulo');

  dep1.doSomething();
  dep2.doOtherThing();
}

exports.myFunction = myFunction;

Requisitando o módulo

// app.js

// Requisita o código de my-function.js e atribui a variável.
var module = require('./my-function');

// Podemos requisitar um módulo idependente de onde estiver
// pois é possível indicar um path, por exemplo:
// var module = require('./assets/javascripts/my-function');

module.myFunction();

Tudo muito simples!

Porém, apesar de ser usado no lado do servidor, é perfeitamente possível utilizarmos em Client-side nos nossos navegadores.

Para isso você vai precisar o Browserify.

Browserify é uma ferramenta de desenvolvimento que nos permite escrever módulos CommonJS que compilam para uso no navegador. Esse artigo explica como funciona o processo.

Futuro/Hoje

Já está sendo elaborada uma nova versão (ES6) para o Javascript.

Atualmente ele está na versão ES5 (ECMAScript), e em breve poderemos utilizar a versão ES6 nos navegadores modernos.

Isso trará muitas coisas interessantes, como seu próprio sistema de módulos.

// ES6

// module.js
module 'myModule' {
  // Importa jQuery local como dependência para o módulo
  import $ from 'jquery';

  // Ou podemos importar por uma URL
  module $ from '//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js';

  var privateVar = 'Private',
    publicVar = 'Public';

  function myMethod() {
    alert(privateVar);
  }

  function myOtherMethod() {
    alert(publicVar);
  }

  function setSomethingToPrivateVar(something) {
    privateVar = something;
  }

  // Exporta os métodos
  export default {
    myCoolMethod: myMethod,
    myOtherMethod: myOtherMethod,
    setSomethingToPrivateVar: setSomethingToPrivateVar,
    // Exporta a variável para termos acesso direto
    publicVar: publicVar
  };
}

// app.js
import myModule from 'myModule';

myModule.setSomethingToPrivateVar('Private Variable');
myModule.myMethod();

Outra coisa interessante é que poderemos definir classes.

// Classes

class Person {
  constructor(name) {
    this.name = name;
  }

  sayHello(message) {
    return this.name + 'diz: ' + message;
  }
}

// Developer herda de Person
class Developer extends Person {
  constructor(name, github) {
    super(name);
    this.github = github;
  }

  // Sobrecarregando o método
  sayHello(message) {
    return this.name + '(' + this.github + ') diz: ' + message;
  }
}

let dev = new Developer('Matheus Azzi', 'matheusazzi');
dev.sayHello('Olá Pessoal!');

Para saber mais sobre as novidades do ES6 Leia aqui.

Qualquer que seja o método que você utilize, deve-se sempre concatenar e compactar seus arquivos.