Módulos JavaScript: Um Guia Para Iniciantes
Esse post foi criado com muita dedicação por mim Jaime Neves e meu amigo Rivail Junior, é uma tradução do post original de Preethi Kasireddy que nos autorizou a fazer a tradução, é muito importante a leitura para você iniciante e também para os mais experientes desenvolvedores JavaScript.
Se você é iniciante em JavaScript, jargões como “module bundlers vs. module loaders,” “Webpack vs. Browserify” e“AMD vs. CommonJS” pode rapidamente tornar-se irresistível.
O sistema de módulos em JavaScript pode lhe deixar meio assustado, mas compreendê-lo é de suma importância para desenvolvedores web.
Neste post, vamos explanar alguns módulos (e alguns exemplos de código). Espero que seja útil para vocês.
Nota: para simplificar, este post será dividido em duas partes: Parte 1 vai mergulhar em módulos explicando o que são e porque usá-los. Parte 2 vamos falar sobre como fazer o carregamento de módulos e as diferentes maneiras de fazê-lo.
Parte 1: Por favor alguém pode explicar novamente o que são módulos ?
Bons autores dividem seus livros em capítulos e secções; bons programadores divide seus códigos em módulos.
Como o capítulo de um livro, os módulos são apenas conjunto de palavras (ou códigos, conforme o caso).
Bons módulos, no entanto, são altamente auto-suficiente com funcionalidades distintas, o que lhes permite ser misturados, removidos ou adicionados conforme a necessidade sem impactar no sistema como um todo.
Porque usar Módulos ?
Existem uma série de benefícios ao usar módulos em favor de uma extensa base de códigos interdependentes. Os mais importantes na minha opinião, são:
1) Manutenibilidade: Por definição, um módulo é auto-suficiente, quando bem projetado visa diminuir as dependências em partes da base de código, tanto quanto possível, de modo que ele possa crescer e melhorar de forma independente. Atualizando um único módulo é muito mas fácil quando o mesmo é dissociado de outros pedaços de códigos.
Voltando ao nosso exemplo do livro, se você quisesse atualizar um capítulo de seu livro, seria um pesadelo se uma pequena alteração em um capítulo, você tivesse que ajustar todos os outros. Em vez disso você gostaria de escrever cada capítulo de tal forma que as melhorias poderiam ser feitas individualmente sem afetar uns aos outros.
2) Namespacing: Em JavaScript, variáveis fora do escopo de uma função de nível superior são globais (ou seja, todos podem acessá-la). Devido a isso é comum ter a “poluição de namespace”, onde completamente existem códigos compartilhados sem referência variáveis globais.
Compartilhamento de variáveis globais entre códigos independentes é inaceitável em desenvolvimento.
Como veremos no final deste post, módulos nos permitem evitar poluição de namespace, criando um espaço privado para nossas variáveis.
3) Reusabilidade: Vamos ser honestos aqui: todos nós já copiamos códigos e escrevemos em novos projetos em um ponto ou outro. Por exemplo, vamos imaginar que você copiou alguns métodos utilitários que escreveu em um projeto anterior no seu projeto atual.
Isso tudo é bom, mas se você encontrar uma melhor maneira de escrever alguma parte desse código, você teria que voltar e lembrar de atualizar em qualquer outro lugar que você escreveu.
Isso é obviamente um enorme desperdício de tempo. Não seria muito mais fácil se houvesse, por exemplo, um módulo que pudesse ser reutilizável ?
Como você pode incorporar módulos ?
Existem muitas maneiras de incorporar módulos em seus programas. Vamos examinar alguns deles:
Padrão de Módulo ( Module Pattern )
O padrão de módulo é usado para imitar o conceito de classes (JavaScript não suporta nativamente o conceito de classes ) assim, podemos armazenar dentro de um único objeto variáveis, métodos públicos e privados semelhante o conceito de classes que são usadas em outras linguagens de programação como Java ou Python. Isso nos permite criar uma API pública revestida com os métodos que queremos expor ao mundo, embora tudo isso seja encapsular variáveis particulares e métodos em um escopo de closure (algo como “fechamento”).
Existem várias maneiras de realizar um padrão de módulo. Neste primeiro exemplo, usarei um anonymous closure (closures anônimos) . Isso vai nos ajudar a atingir nosso objetivo, colocando todo o nosso código em uma função anônima. ( Lembre-se em JavaScript, funções são a única maneira de criar o novo escopo.)
Exemplo 1: Fechamento Anônimos ( Anonymous Closure )
Com essa construção, nossa função anônima tem seu próprio ambiente de avaliação ou “closure”, e então nós imediatamente calculamos. Isso permite-nos esconder variáveis do namespace (global) pai.
O que é legal nessa abordagem é que você pode usar variáveis locais dentro desta função sem sobrescrever acidentalmente as variáveis globais existentes, contudo ainda acessar as variáveis globais, da seguinte forma:
Observe que os parênteses em torno da função anônima são necessários, pois instruções que começam com a palava-chave function são sempre consideradas declarações de função, e desta maneira estamos encapsulando um bloco de código através da criação desta estrutura, se você estiver curioso sobre isso, leia mas aqui.
Exemplo 2: Importação Global ( Global Import )
Outra abordagem popular usadas por bibliotecas como o jQuery é global import. É similar ao fechamento anônimo “anonymous closure” que acabamos de ver, só que agora passamos a variável global como parâmetros:
Neste exemplo, globalVariable é a única variável que é global. A vantagem desta abordagem sobre fechamento anônimo “anonymous closure” é que você declare as variáveis globais inicialmente, tornando-se claro para as pessoas lendo o seu código.
Exemplo 3: Interface de Objeto ( Object Interface)
Ainda um outra abordagem é criar módulos usando uma interface de objeto independente, da seguinte forma:
Como você pode ver, essa abordagem permite-nos decidir quais variáveis/métodos que queremos manter privado (por exemplo: minhasNotas) e o que queremos expor colocando-os na instrução de return (por exemplo: average & failing).
Exemplo 4: Revelando Módulo Padrão (Revealing Module Pattern)
Este é muito parecido com a abordagem acima, exceto que garante que todos os métodos e variáveis são mantidos em sigilo, até explicitamente expostos:
Isso pode parecer muita coisa para digerir, mas quando se trata de padrões de módulos isso é apenas a ponta do iceberg. Aqui estão alguns dos recursos úteis que encontrei em minhas explorações:
- Learning JavaScript Design Patterns: por Addy Osmani: Um tesouro de detalhes em uma leitura impressionante sucinta
- Adequately Good by ben Cherry: Uma visão geral útil com exemplos avançados usando módulo padrão
- Blog of Carl Danley: Visão geral sobre padrão de módulo e recursos para outros padrões JavaScript.
CommonJS e AMD
As abordagens acima todas têm uma coisa em comum: o uso de uma única variável global para envolver seu código em uma função, criando assim um namespace privado para si, usando um closure scope (escopo de fechamento).
Enquanto cada abordagem é eficaz em sua própria maneira, elas têm suas desvantagens.
Por um lado, como um desenvolvedor, você precisa saber a ordem correta das dependências para carregar seus arquivos. Por exemplo, digamos que você está usando o Backbone em seu projeto, assim você precisa incluir a tag script para o código-fonte do Backbone no seu arquivo.
No entanto, visto que Backbone tem uma forte dependência em Underscore.js, a tag script para o Backbone não pode ser colocado antes do arquivo Underscore.js.
Como desenvolvedor, gerenciar as dependências e organizá-las pode dar um pouco de dor de cabeça.
Outra desvantagem é que eles ainda podem levar a conflitos de namespace. Por exemplo, se dois de seus módulos têm o mesmo nome? Ou se você tem duas versões de um módulo, e precisar de ambos?
Então você deve está se perguntando: nós podemos projetar uma maneira de pedir a interface de um módulo sem passar pelo escopo global?
Felizmente, a resposta é sim.
Existem duas abordagens populares e bem implementadas: CommonJS e AMD.
CommonJS
CommonJS é um grupo de trabalho voluntário que projeta e implementa APIs JavaScript para declarar os módulos.
Um módulo de CommonJS é essencialmente um pedaço reutilizável de JavaScript que exporta objetos específicos, tornando-os disponível para que outros módulos possa chamá-lo em seus programas. Se você programa em Nodejs está familiarizado com este formato.
Com CommonJS, cada arquivo JavaScript armazena módulos em seu próprio contexto de módulo único (exatamente como envolvê-lo em um closure). Neste escopo, podemos usar o objeto module.exports para expor os módulos e require para importá-los.
Quando você estiver definindo um módulo de CommonJS, ele pode parecer com isso:
nós usamos o module de objeto especial e passamos a referência da nossa função dentro do module.exports. isso permite que o sistema de módulos do CommonJS saiba o que queremos expor e que outros arquivos podem consumi-lo.
Então quando alguém quiser usar o myModule, eles podem chamá-lo no arquivo, da seguinte forma:
Existem dois benefícios óbvios sobre essa abordagem de module pattners.
- Evitar poluição de namespace global
- Criar nossas dependências explícitas
Alem disso, a sintaxe é bem compacta, que eu particularmente amo.
Outra coisa a ser notada é que o CommonJS usa uma abordagem serve-first e sincronicamente carrega os módulos. Isso é importante, pois caso tenhamos três outros módulos que precisamos chamar, isso carregará todos eles, um a um.
Agora, que isso funciona perfeitamente no server, mas infelizmente, torna-se difícil de usá-lo quando escrito em JavaScript para o browser. Basta dizer que ler um modulo da web leva muito mais tempo do que ler de um disco. Enquanto um script para carregar um modulo estiver sendo executado, isso bloqueará o browser do processamento de qualquer coisa ate que isso termine de carregar. Esse comportamento acontece por que a thread do JavaScript para, até que o código esteja completamente carregado.
AMD
CommonJS é tudo de bom, mas e se nós quisermos carregar módulos assincronicamente? a resposta é AMD (definição assíncrona de módulos).
Carregar módulos usando amd, pode ser algo semelhante a isso.
O que acontece aqui é que a função define toma como seu primeiro argumento um array das dependência dos módulos. As dependências são carregadas em background e uma vez carregadas, o define chama a função de callback que foi dada.
Próximo, a função de callback leva, como argumento, as dependências que foram carregadas — no nosso caso, myModule e myOtherModule — permitindo a função de usar essa dependências. Finalmente, as próprias dependências também devem ser definidas usando a palavra chave define.
Por exemplo, myModule deve se parecer com isso:
Então, ao contrario do CommonJS, o AMD tem a abordagem de browser-first juntamente com comportamento assíncrono para obter o trabalho feito.( Nota, há muitas pessoas que acreditam fortemente que o carregamento fragmentado de arquivos dinamicamente assim que começar a executar o código não é favorável, exploraremos mais esse assunto, na próxima seção sobre construção de módulo).
Além de assincronia, outro benefício do AMD é que seus módulos podem ser objetos, funções, construtores, strings JSON e muitos outros tipos, enquanto CommonJS suporta apenas objetos como módulos.
Dito isso, a AMD não é compatível com io, filesystem e outras características server-oriented disponíveis via CommonJS, e as funções que envolvem a sintaxe são um pouco mais verbosas comparada com a simples require statement.
UMD
Para projetos que necessitam que você tenha suporte para as duas características AMD e CommonJS, existe um outro formato UMD (Universal Module Definition).
UMD cria essencialmente uma maneira de usar as duas definições, apoiandi também a definição de variável global. Como resultado, módulos UMD são capazes de trabalhar tanto no cliente quanto no servidor.
Aqui vai um exemplo de como o UMD trabalha.
Para obter mais exemplos de formatos UMD link do repositório no GitGub.
Native JS
Ufa! Você ainda esta por ai? ainda não perdi você? Ótimo! Pois ainda temos mais uma definição sobre módulos antes de terminarmos.
Como você deve ter percebido, nenhum dos módulos apresentados eram JavaScript nativo. Em vez disso, temos criado jeitos de emular sistemas de módulos usando cada padrão de projeto, CommonJS ou AMD
Felizmente, as pessoas “inteligentes” no TC39 (o corpo de normas que define a sintaxe e a semântica do ECMAScript), introduziram um buid-in modules com ECMAScript 6 (ES6).
ES6 oferece uma grande variedade de possibilidades para importação e exportação de módulos que outros têm feito um bom trabalho explicando — aqui estão alguns desses recursos:
O que é grande no ES6 modules em relação ao CommonJS ou AMD é como ele consegue oferecer o melhor dos dois mundos: sintaxe declarativa e compacta e carregamento assíncrono, além de benefícios adicionais, como melhor suporte para dependências cíclicas.
Provavelmente a minha característica favorita do ES6 modules é que as importações são live read-only views das exportações. (Compare isso com CommonJS, onde as importações são cópias das exportações, e consequentemente não vivas).
Aqui está um exemplo de como isso funciona:
Neste exemplo, fizemos basicamente duas cópias do módulo, quando exportamos e quando o chamamos.
Além disso, a copia dentro da main.js está desconectada do modulo original. Isso por que sempre que implementamos nosso contador ele retorna 1- porque a variável contadora que importamos é uma copia desconectada da variável contadora do modulo.
Então, incrementando o contador ira incrementá-lo dentro do modulo, mas não incrementará sua versão copiada. o único jeito de modificar a versão copiada do contador é fazer isso manualmente.
Por outro lado, ES6 cria uma exibição live read-only views dos módulos que importamos:
Ótimo material, né? o que eu acho realmente interessante sobre live read-only view é como eles permitem você dividir seus módulos em pedaços menores sem perder a funcionalidade.
Então você pode virar e mesclá-los novamente, não há problema. Ele só “funciona”.
Olhando para frente. construindo módulos
Ual, o tempo voa né ? rsrs, sinceramente espero ter dado a você um melhor entendimento sobre modules em JavaScript.
