Escrevendo módulos JavaScript compatíveis com o Browser, AMD e Node.js
Eu recentemente comecei a criar um módulo para agendar a execução de eventos e imediatamente fiquei me confrontado com a questão da reutilização de código entre o cliente e o servidor. Este módulo chamado Schedulejs que eu desenvolvi utilizando algumas técnicas simples pode ser utilizado pelo cliente e pelo servidor. Neste pequeno artigo vou explicar um pouco como escrever módulos cujo código pode ser facilmente reutilizados no navegador e Node.js.
Então vamos supor que queremos desenvolver um módulo com um objeto
Utilitários que é composto por funções uteis para criação de uma
aplicação ou um novo módulo. Se fossemos criá-lo apenas para ser executado
pelo Node.js criaríamos um arquivo chamado "utilitarios.js
" com o seguinte
conteúdo:
var Utilitarios = (function () {
var Utilitarios = function (options) {};
Utilitarios.prototype.isNull = function (obj) {
return obj === null;
};
Utilitarios.prototype.isUndefined = function (obj) {
return obj === void 0;
};
return Utilitarios;
})();
module.exports = Utilitarios;
E depois acessaríamos este módulo utilizando a função require()
:
var Utilitarios = require('./utilitarios'),
util = new Utilitarios(),
saldo = null;
if (util.isNull(saldo)) {
console.log('Seu saldo é 0');
}
Para usá-lo no cliente o que pode ser feito da seguinte maneira:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Minha aplicação</title>
<script src="utilitarios.js"></script>
<script>
var util = new Utilitarios(),
saldo = null;
if (util.isNull(saldo)) {
alert('Seu saldo é 0');
}
</script>
</head>
<body></body>
</html>
O código acima lançara um erro dizendo que a variável module
utilizada em
utilitarios.js
não foi definida. Porém o objeto Utilitarios
é exportado ao
contrário do que acontece no Node.js que apenas é exportado utilizando o método
module.exports
. Para corrigir este comportamento indesejado devemos primeiro
lidar com a definição do método module
e ocultar as informações do código.
A solução seria buscarmos pela variável module
e verificar se a mesma tem um
método chamado exports
e como no Browser tudo é associado a um objeto
window
então adicionamos o modulo como um novo método deste objeto. Além
disso para ocultamos as informações devemos envolver todas as informações
dentro de uma função autoexecutável. No final o código do módulo resultante
seria semelhante a este:
(function () {
var Utilitarios = (function () {
var Utilitarios = function (options) {};
Utilitarios.prototype.isNull = function (obj) {
return obj === null;
};
Utilitarios.prototype.isUndefined = function (obj) {
return obj === void 0;
};
return Utilitarios;
})();
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = Utilitarios;
} else {
window.Utilitarios = Utilitarios;
}
})();
Caso você queira que seu modulo ofereça suporte a AMD modifique o bloco de exportação por esta forma:
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = Utilitarios;
} else if (typeof define === 'function' && define.amd) {
define([], function () {
return Utilitarios;
});
} else {
window.Utilitarios = Utilitarios;
}
No Schedulejs eu estou utilizando estas técnicas porém um pouco diferente baseando-se no método adotado pela comunidade de desenvolvimento do Backbonejs e Jquery.
(function (global, factory) {
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = factory(global);
} else if (typeof define === 'function' && define.amd) {
define([], function () {
return factory(global);
});
} else {
global.Utilitarios = factory(global);
}
})(typeof window !== 'undefined' ? window : this, function (window) {
var Core = function (options) {};
Core.prototype.isNull = function (obj) {
return obj === null;
};
Core.prototype.isUndefined = function (obj) {
return obj === void 0;
};
return Core;
});