Uma breve história sobre módulos em Javascript - Parte I

Uma breve história sobre módulos em Javascript - Parte I

O que era uma revisão, precisa de uma explicação e de uma série de artigos abordandos vários assuntos relacionados, então vamos falar sobre os módulos um pouco da história deles no Javascript, boas práticas, ferramentas e life cicle do Node.JS em uma série de artigos.

Alguns exemplos citados nos artigos tem a narrativa é usada no passado para facilitar a construção de linha do tempo, mas ainda podem ser aplicado hoje em dia e não são necessariamente relacionados ao Node.

Uma classe por script

Quem chegou recentemente no Javascript ou não teve contato com desenvolvimento front-end, talvez não conheça a fundo essa abordagem, mas quando queríamos escrever uma classe nos padrões antigos da linguagem, escrevíamos uma função que ficava disponível globalmente e era acessada através de outros scripts dentro da mesma página.

Era bem comum ver as declarações algo assim:

<script src="script-1.js"></script>
<script src="script-2.js"></script>

Isso facilitava a separação do código, mas trazia problemas.

Por restrições do HTTP 1 só 3 arquivos podiam ser carregados por endereço, deixando a página lenta no carregamento já que aguardando o download do CSS, HTML e vários arquivos JavaScript.

A solução encontrada para resolver esse problema foi a concatenação de arquivos e minificação.

Outro problema era a falta de padrão na escrita dos módulos Javascript, alguns usavam o padrão de Orientação a Objetos por protótipos, outros usavam Objetos Literais.

O padrão através de objetos por protótipos eram declarados como abaixo:

// index.js
function Animals (qnty) {
  this.legs = qnty;
}

Animals.prototype.walk = function walk() {
  return !!this.legs;
}
// index-2.js
var cat = new Animals(4);
cat.walk() // true;

Já os objetos literais eram bem parecidos com o que usamos nos nosso JSONs.

// index.js
var Animals = {
  legs: 0,
  constructor: function constructor(qnty) {
    this.legs = qnty; 
    return this;
  },
  walk: function walk() {
    return !!this.legs;
  }
};
// index-2.js
var fish = Animals.constructor(0);
fish.walk() // false

Mas essa abordagem tinha alguns problemas, ao usarmos o objeto inicial, alteravamos a propriedade de Animals afetando todas as declarações anteriores.

Usando nosso exemplo poderiamos ter algo assim:

// index-3.js
var cats = Animals.constructor(4);
cats.walk(); // true
fish.walk(); // true

Isso acontece por conta das referencias em objetos, algo que quero explicar melhor no futuro e com bastante profundidade, mas que é muito importante dentro do Javascript.

Sabendo desse problema, precisavamos de algo melhor e que pudesse ter propriedades privadas. Já que o acesso de uma propriedade poderia ser fácilmente modificada.

IIFE - Immediately Invoked Function Expression

Tenho quase certeza que você já viu algo assim em algum código (function(){})(), o padrão de expressões de funções auto invocavéis vem para resolver o problema de propriedades não privadas e conflitos entre módulos que sobreescrevem propriedades.

Vamos rever o nosso exemplo usando essa forma?

var Animals = (function(legsQnty) {
   var legs = legsQnty || 4;
   
   return {
     walk: function walk() {
       return !!legs;
     }
   };
});
var cat = Animals(4);
cat.walk() // true

Essa forma cria um isolamento dentro da função, legs usado em no exemplo acima está isolado de alteraçõesde suas propriedades e cria uma aparencia semelhante aos construtores de linguagens orientadas a objeto. Também podemos definir propriedades padrões e desacoplar.

Isso já resolve vários problemas, mas não fica isento dos seus problemas e ainda não se aproxima de uma implementação puramente nativa de módulos.

Alguns problemas são facilidade da função contida em uma variavel deixar de existir por uma declaração algo como Animals = '', é uma sintaxe estranha e pouco sem um bom padrão sendo uma alternativa personalizada.

Solução para o Javascript no servidor - CommonJS

No inicio de 2009, Kevin Dangoor engenheiro na Mozilla e hoje na Khan Academy, escreveu em seu blog sobre algumas necessidades para ter Javascript no servidor, afinal vários projetos para rodar Javascript no servidor estavam acontecendo naquele momento, para criar alguns padrões se iniciou o grupo ServerJS que mais tarde se tornou o CommonJS.

What I’m describing here is not a technical problem. It’s a matter of people getting together and making a decision to step forward and start building up something bigger and cooler together. - Kevin Dangoor

Em maio de 2009, Ryan Dahl criou o NodeJS e logo seguiu as especificações para módulos do CommonJS e ainda naquele ano, foi criado o NPM.

CommonJS, guiou para a melhor forma de fazer, Node adotou, mas com o tempo acabou abandonando para implementar um fluxo próprio, mas ainda assim seguiu muito especificação e criou o estado atual que conhecemos dos módulos no Node.JS

A abordagem de módulos no servidor agora ficariam definidas da seguinte forma

module.exports = function Animals(legsQnty) {
  var legs = legsQnty || 4; 
  return {
    walk: function walk() {
      return !!legs;
    }
  };
  return {
  }
}

var cat = require('./animals.js');
cat(4).walk()

Dessa forma ficou mais claro as formas de declarações, ajudou a preparar o próximo passo da evolução do Javascript.

Os ganhos que temos é a carregamento externo do módulos, uma sintaxe mais concisa, o escopo das variaveis ficam melhores e traz a boa prática de não termos variavéis globais.

Mas não funciona muito bem em ambientes assincronos, todas as chamadas do require precisam acontecer antes da execução do código, com isso precisamos do passo de compilação do Javascript. Isso traz de volta o problema relacionado a conexão, quando se trata de front-end devido as conexões lentas.

No próximo artigo vamos falar sobre os padrões AMD, UMD e também os ES6
Modules. Espero que tenha gostado dessa primeira parte e qualquer dúvida só me chamar. :)


Referências