Front End

2 ago, 2013

Lazy Load em Javascript com RequireJS

Publicidade

Como os sites estão cheios de mais e mais JavaScript, imagens e outros ativos externos, sites têm se tornado cada vez mais inchados, e o desempenho começou a se tornar um problema. Nós começamos a reduzir o número de pedidos concatenando nossos arquivos CSS e JavaScript e usando sprites de imagem. Nós temos reduzido o tamanho dos arquivos minimizando e ofuscando o nosso código e otimizado nossas imagens. Todas essas medidas são boas, mas elas podem não ser o suficiente. Se você tem uma grande aplicação JavaScript, você pode ter uma tonelada de JavaScript sendo carregada nesse único arquivo concatenado, e uma grande parte do código pode estar indo para o lixo porque ele não é usado. Vamos tentar carregar alguns desses códigos de forma tardia, utilizando RequireJS.

Este artigo pressupõe que você tenha algum conhecimento de RequireJS. Se você não tiver, então eu sugiro que assista parte 5 do passo a passo da minha aplicação Backbone, na qual eu discuto um pouco sobre RequireJS e como converter um aplicativo para usá-lo. Para uma introdução mais completa, você pode ler JavaScript Modular com RequireJS.

O que é o Lazy Load?

Tenho certeza de que muitos de vocês que vêm a este artigo já têm um entendimento de Lazy Load, e se você é uma daquelas senhoras ou cavalheiros finamente educados, então sinta-se livre para pular para a próxima seção, o que eu tenho certeza de que muitos já fizeram imediatamente depois de ler o título. No entanto, para aqueles que não tenham sido formalmente apresentados, vamos preencher esse vazio para você.

Normalmente, todo JavaScript que pertence a uma determinada página web ou aplicativo é carregado imediatamente durante o carregamento da página. Claro, nem todo usuário irá interagir com tudo na sua página, assim, grande parte do código será apenas um desperdício de banda larga do usuário, o que pode ser particularmente doloroso para as pessoas que têm uma quantidade limitada de uso de dados, como muitos dispositivos móveis e planos de serviço de Internet via satélite. Também faz com que as pessoas com conexões lentas de Internet (e até mesmo as pessoas com conexões de alta velocidade) tenham que esperar o código carregar, mesmo que ele não seja usado.

Concatenação e minificação funcionam para reduzir a dor causada pelo código desperdiçado, mas não o elimina e, na maioria das vezes, é impossível eliminar completamente tudo. Há quase sempre um código que não é utilizado por um usuário, mas Lazy Load pode ajudar muito a aliviar a quantidade de código desperdiçado. Lazy Load é carregamento de código apenas quando o usuário precisa. Então, se você tem um botão em sua página que irá mostrar uma tela completamente diferente para o usuário, uma vez que é pressionado, então não há nenhum nexo em colocar o código para aquela tela imediatamente. Você pode, em vez disso, carregá-lo assim que o botão for pressionado. Isso pode causar um pequeno atraso, mas nada com que o usuário não possa lidar, especialmente porque isso só vai acontecer na primeira vez, porque, depois disso, o código já estará carregado, e se você tiver ativado o cache, ele pode ser armazenado em cache para a próxima visita. A questão toda é não carregar scripts até que eles sejam necessários. Isso pode parecer difícil, mas, como você verá na próxima seção, é realmente muito simples.

Fazendo o RequireJS atrasar o carregamento

RequireJS é o segredo que faz o Lazy Load simples. Se você já não estiver usando-o para gerenciamento de dependência, leia um tutorial e comece a usá-lo. Ou você pode usar alguma outra biblioteca de gerenciamento de dependência, mas eu recomendo RequireJS, e esse tutorial só vai falar sobre RequireJS.

A chave para o Lazy Load é estar reagindo ao input do usuário. Então, como eu disse na seção anterior, se um usuário clica em um botão que carrega uma tela inteiramente nova, o código para aquela tela só deve ser carregado depois que o botão for pressionado. Então, nós simplesmente precisamos usar RequireJS para require algum código dentro do botão manipulador de eventos.

[javascript]

$(‘#somebutton’).on(‘click’, function() {
require(
[‘every’, ‘javascript’, ‘dependency’, ‘for’, ‘other’, ‘screen’],
function(ev, js, dep, fr, othr, scrn){
// Create the screen with your dependencies
}
);
});

[/javascript]

A maior diferença entre o uso “normal” de RequireJS e usá-lo para Lazy Load é que você usa require dentro de uma função que é chamada em um momento posterior (como quando um botão é clicado). Isso é sério, a única diferença.

É claro que bons desenvolvedores não vão encher seus manipuladores de eventos com as declarações require. Bons desenvolvedores organizam o código e preocupações distintas em diferentes objetos e funções. Então, vamos dar uma olhada em como podemos realizar tudo isso com uma aplicação Backbone padrão. No exemplo abaixo, você vai ver que eu estou mantendo uma grande parte do trabalho dentro do router. Isso é realmente amplamente aceito, mas eu prefiro manter essa lógica em um controlador separado em meus próprios aplicativos. Eu estou usando o router aqui para simplificar o código um pouco e porque é muito bem conhecido como o roteador funciona normalmente para aplicações de Backbone. Além disso, vamos fazer

[javascript]

// View logic
AView = Backbone.View.extend({
events: {
‘click button’: ‘edit’
},

edit: function() {
var id = this.getId(); // Just some type of calculation
App.router.navigate(‘thing/’ + id, {trigger: true});
}
});

Router = Backbone.Router.extend({
routes: {
‘thing/:id’: ‘edit’
},
edit: function() {
require(
[‘every’, ‘javascript’, ‘dependency’, ‘for’, ‘edit’, ‘screen’],
function(ev, js, dep, fr, edit, scrn){
// Create the screen with your dependencies
}
);
}
});

[/javascript]

Então, basicamente, tudo o que eu fiz foi colocar toda a lógica de Lazy Loading e arrumar as coisas para o roteador, que – a menos que você esteja usando um controlador – é onde ele deve estar.

lazy_loading

O Lazy Load

No meu projeto atual, eu realmente criei uma abstração a partir disso, de modo que o controlador não precisa mexer com RequireJS diretamente. Ela é chamada de LazyLoader.

[javascript]

var LazyLoader = function(type) {
this.type = type;
};

_.extend(LazyLoader.prototype, {
get: function() {
var fileNames = Array.prototype.slice.call(arguments);
var dfd = $.Deferred();
var path = this.type + "/";

fileNames = _.map(fileNames, function(fileName){
return path + fileName;
});

require(fileNames, function() {
dfd.resolve.apply(dfd, arguments);
});

return dfd.promise();
}
});

[/javascript]

O construtor recebe um único parâmetro, que é então usado como o diretório para recursos que você está tentando carregar. Em seguida, use o método get para recuperar qualquer número de dependências. A função retorna um compromisso, no qual você pode utilizar usar then ou done para completar realmente o que você precisa fazer. Por exemplo:

[javascript]

var loader = new LazyLoader(‘views’); // Now when I request a file, it’ll be from views/*

// Load one resource and work with it
loader.get(‘some-module’).then( function(SomeModule) {
// set up SomeModule;
});

// Or you can load multiple, just like RequireJS
loader.get(‘some-module’, ‘another-module’, ‘one-more-module’).then( function(Mod1, Mod2, Mod3) {
// Use the modules
});

[/javascript]

A razão pela qual eu fiz isso é dupla. Primeiro de tudo, se eu decidir usar uma biblioteca diferente do RequireJS para carregar os módulos no futuro, eu simplesmente tenho que atualizar o LazyLoader em vez de procurar em todos os lugares que eu usei Lazy Load. A outra razão é porque então eu posso fazer uma simples API para obter um recurso dentro do meu aplicativo. Eu simplesmente anexo diferentes carregadores para certas propriedades do meu objeto de aplicações. Em uma aplicação em que o padrão LazyLoader não é usado, muitas pessoas vão juntar todas as classes view em App.Views, e coisas assim. Em uma aplicação em que não sabemos se a view foi carregada, precisamos de uma maneira para nos certificar de sua carga, mas eu ainda quero que ela seja encontrado em App.Views. Então, eu uso o LazyLoader assim:

[javascript]

App.Views = new LazyLoader(‘views’);
App.Models = new LazyLoader(‘models’);

// Now we want to use a view
App.Views.get(‘some-view’).then(…);

[/javascript]

Parece fazer sentido pegar uma classe views usando App.Views.get, não é mesmo? É por isso que eu fiz o Lazy Load em vez de apenas ficar com código RequireJS. É claro o que você está fazendo.

Claro que isso apresenta um problema quando você precisa carregar os recursos de diferentes tipos, por exemplo, uma view e um modelo. Mas a API tem uma forma de lidar com isso. Eu gosto de cuidar disso desta forma:

[javascript]

var getView = App.Views.get(‘some-view’);
var getModel = App.Models.get(‘some-model’);

$.when(getView, getModel).then( function(SomeView, SomeModel) {
// Use SomeView and SomeModel
});

[/javascript]

Se você entender como usar os compromissos, então tudo isso vai fazer sentido. Se você não entender como usar os compromissos, então eu sugiro que você leia sobre eles. Existem algumas armadilhas estranhas com o modo como os parâmetros são transmitidos para a função then acima. Vou te dar alguns exemplos para mostrar o que eu quero dizer:

[javascript]

// Request one file per call to ‘get’
var getView = App.Views.get(‘some-view’);
var getModel = App.Models.get(‘some-model’);

$.when(getView, getModel).then( function(param1, param2) {
// param1 = the module from ‘some-view’
// param2 = the module from ‘some-model’
});

// Request multiple files from one ‘get’
var getView = App.Views.get(‘some-view’, ‘other-view’);

$.when(getView).then( function(param1, param2) {
// param1 = the module from ‘some-view’
// param2 = the module from ‘other-view’
});

// Request multiple files with multiple calls to ‘get’. This is where it gets interesting
var getView = App.Views.get(‘some-view’, ‘other-view’);
var getModel = App.Models.get(‘some-model’);

$.when(getView, getModel).then( function(param1, param2) {
// param1 = array -> [module from ‘some-view’, module from ‘other-view’]
// param2 = the module from ‘some-model’
});

// Another multiple x multiple
var getView = App.Views.get(‘some-view’);
var getModel = App.Models.get(‘some-model’, ‘other-model’);

$.when(getView, getModel).then( function(param1, param2) {
// param1 = the module from ‘some-view’
// param2 = array -> [module from ‘some-model’, module from ‘other-model’]
});

// Another multiple x multiple
var getView = App.Views.get(‘some-view’, ‘other-view’);
var getModel = App.Models.get(‘some-model’, ‘other-model’);

$.when(getView, getModel).then( function(param1, param2) {
// param1 = array -> [module from ‘some-view’, module from ‘other-view’]
// param2 = array -> [module from ‘some-model’, module from ‘other-model’]
});

[/javascript]

Espero que você entenda como isso está funcionando, porque eu realmente não quero colocar em palavras. De qualquer forma, se você não quer lidar com arrays de módulos sendo passados, então você pode mudar esse último exemplo para algo como isto:

[javascript]

var getSomeView = App.Views.get(‘some-view’);
var getOtherView = App.Views.get(‘other-view’);
var getSomeModel = App.Models.get(‘some-model’);
var getOtherModel = App.Models.get(‘other-model’);

$.when(getSomeView, getOtherView, getSomeModel, getOtherModel).then(
function(SomeView, OtherView, SomeModel, OtherModel) {
// There, now each of your modules have their own parameter again.
});

[/javascript]

Conclusão

Eu acho que isso acabou sendo mais uma introdução à forma como eu uso o Lazy Load  do que sobre como usar o RequireJS para isso, mas eu consegui passar a minha mensagem. Experimente-o, veja como você gosta dele, e veja o quanto ele mais rápido ele ao carregar a sua aplicação! Cabe a você decidir se vale a pena converter 5 segundos de download inicial para um 1 segundo de download inicial com outros pequenos downloads espalhados aqui e ali, mas, no final, seus usuários são os únicos que vão decidir se vão utilizá-los de acordo com os tempos de download. Espero que eu tenha lhe dado conhecimento suficiente de como fazê-lo, de modo que você possa tornar seus apps melhores.

***

Artigo traduzido pela Redação iMasters, com autorização do autor. Publicado originalmente em http://www.joezimjs.com/javascript/lazy-loading-javascript-with-requirejs/