Front End

16 nov, 2011

Organize seu código com RequireJS

Publicidade

Escrever aplicações web usando JavaScript, HTML e CSS pode, rapidamente, se tornar cansativo. Além do gerenciamento de dados, UI, interações, e requisições de rede, o código do aplicativo pode se tornar uma verdadeira bagunça, e isso não melhora de acordo com o modo com que o navegador carrega o código JavaScript.

Na maioria dos outros ambientes de programação, existe alguma maneira de dividir seu código em módulos, ou scripts, e requisitar um módulo do outro.

No JavaScript, podemos dividir nosso código em vários arquivos, mas não existe como declarar em um script que você está dependendo de outro. Pior ainda, o lugar onde os scripts são carregados não está nem na mesma linguagem que você está escrevendo seu código. Você carrega todas as suas dependências JavaScript do seu documento HTML, não dos seus arquivos JavaScript. Isso pode funcionar para incluir um pequeno Ajax aqui, e uma animação ali, mas para grandes aplicativos de página única, isso não funciona.

Eu tive a oportunidade de trabalhar em um grande aplicativo de página única para a Treehouse, e experimentei usar o RequireJS; e eu adorei!

Como o RequireJS deixa a construção de um aplicativo mais fácil?

Definição de Módulo

Todo seu código é escrito em módulos auto-contidos. Módulos são pequenos pedaços da sua aplicação que servem para um objetivo específico. É você quem define a quantidade de código existente em um módulo. Você poderia escrever todo seu app em um módulo, mas isso iria acabar com a organização e habilidade de extrair, ou substituir porções do seu app facilmente.

Um simples módulo típico é definido assim:

define(function () {

function method (x) {
return x + x;
}

return {
someValue: 'foobar',
myMethod: method
}
});

Aqui usamos a função define() para definir nossa função. Nesse exemplo, passamos uma função que irá retornar o nosso módulo. Nesse caso, nosso módulo é um objeto com duas propriedades: uma string someValue e uma função myMethod.

Este código valeria por todo nosso arquivo JavaScript para esse módulo. Nada deveria ser declarado fora de uma única chamada define.

Se este código fosse salvo em my/utils.js, este módulo seria definido como “my/utils”.

Dependências de Módulo

Definir módulos é ótimo, mas vamos precisar usar partes de outros módulos. Quando definimos um módulo, podemos passar uma lista de nomes deles, e o RequireJS irá garantir que eles sejam recuperados antes que seu módulo seja executado, e irá passá-los como parâmetros da função de definição.

Dado que ainda temos o módulo “my/utils”, e o “models/Person”, que define uma única classe JavaScript Person, aqui está como deveríamos definir um módulo que requer ambos:

define(["models/Person", "my/utils"], function (Person, utils) {
var people = [];

people.push(new Person('Jim'));
people.push(new Person(utils.someValue));


return {people: people};
});

Aqui definimos um módulo que diremos que vive em “app/people.js”. Ele precisou de dois módulos, “models/Person”, e “my/utils”. Esses módulos foram, então, passados para nossa função de definição de módulo, e nós os ligamos aos parâmetros Person e utils. Poderíamos chamar os parâmetros de qualquer coisa, mas eu escolhi refletir os nomes dos módulos.

Neste exemplo, eu capitalizei o módulo Person, uma vez que nesse módulo hipotético ele está retornando à uma classe JavaScript, e não um objeto normal com propriedades, então, eu usei um nome capitalizado como uma convenção.

O módulo, então, definiu uma array de pessoas, adicionou algumas Persons a ela, e a expôs como uma propriedade de seu próprio módulo.

O RequireJS olha para as dependências de todos os módulos no seu aplicativo, e busca e executa todos os módulos na sua ordem correta, de modo que cada módulo tem exatamente o que ele precisa para rodar.

Rodando o App

Agora que temos nossos módulos definidos, queremos usá-los para de fato iniciar uma aplicação. Isso normalmente será feito em algum tipo de arquivo “principal”, o que não é, de fato, um módulo.

Ao invés de utilizar define() para envolver nosso código, no nosso arquivo principal usaremos require(). Esta função é parecida, com isso, você pode passar uma array de dependências opcional, e a função que será executada quando essas dependências forem resolvidas. No entanto, esse código não é armazenado como um módulo nomeado, uma vez que seu objetivo é ser executado imediatamente, muito parecido com a função main() em um programa C.

Então digamos que nosso arquivo principal está em “/scripts/main.js”, e que nossos módulos foram definidos sob “/scripts/”. Então, na verdade “my/utils” vive em “/scripts/my/utils.js”, e “models/Person” vive em “/scripts/models/Person.js”, e assim por diante. Depois iremos ver como isso é importante. Mas nosso main.js deve ser parecer com isso:

require(["app/people"], function(people) {
alert(people.people[0]);
});

Aqui, nós apenas requisitamos o “módulo app/people”, e o ligamos a people. O módulo people expos uma propriedade people, sobre a qual alertamos o primeiro elemento.

Então, agora precisamos incluir o JavaScript na nossa página HTML. Fazemos isso com uma tag script.

<script data-main="scripts/main" src="scripts/require.js"></script>

Nossa estrutura de arquivo se parece com isso:

root/ 
index.html
scripts/
require.js
main.js
app/
people.js
models/
Person.js
my/
utils.js

Nossa tag script está incluindo, através do atributo src, o script require.js do diretório de scripts. Também definimos um atributo chamado data-main, com o nome do nosso módulo de aplicativo principal, “scripts/main”. Isso significa que o RequireJS irá carregar o “/scripts/main.js” como o ponto principal de entrada.

Ele também configura o diretório raíz para módulos para o diretório “/scripts/”. É por isso que chamamos de módulo o “my/utils”, quando ele, na verdade, vive no “/scripts/my/utils.js”. Você pode requisitar um módulo a partir de um endereço absoluto ao dar seu nome um “./” ou “/”, ou uma uma URL completa. Caso contrário, ele assume que o módulo vive relativo ao script principal.

Otimização

Enquanto ter o código dividido em vários arquivos é ótimo para otimização, é terrível para performance. É por isso que o RequireJS inclui um otimizador que irá pegar os módulos do seu app, movê-los para um único arquivo, e reduzi-los para melhorar a performance.

Bônus: RequireJS + Dojo = Demais!

Meu aplicativo usa Dojo, e toda a UI está construída usando Dojo Widgets customizados. Acontece que o carregador do Dojo é compatível com o RequireJS, se você configurar sua aplicação corretamente. Isso deixou meu aplicativo muito mais fácil de desenvolver e manter.

Além disso, usar o CoffeeScript para ampliar o Dojo Widgets também é incrível, mas isso fica para outro artigo!

***

Texto original disponível em http://thinkvitamin.com/code/javascript/organize-your-code-with-requirejs/