JS, Node.js, Frontend, Backend, Firebase, Express, Patrones, HTML5_APIs, Asincronía, Websockets, Testing
En programación, un módulo es una porción de un programa de ordenador. De las varias tareas que debe realizar un programa para cumplir con su función u objetivos, un módulo realizará, comúnmente, una de dichas tareas (o varias, en algún caso).
En general (no necesariamente relacionado con la programación), un módulo recibe como entrada la salida que haya proporcionado otro módulo o los datos de entrada al sistema (programa) si se trata del módulo principal de éste; y proporcionará una salida que, a su vez, podrá ser utilizada como entrada de otro módulo o bien contribuirá directamente a la salida final del sistema (programa), si se retorna al módulo principal.
Particularmente, en el caso de la programación, los módulos suelen estar (no necesariamente) organizados jerárquicamente en niveles, de forma que hay un módulo principal que realiza las llamadas oportunas a los módulos de nivel inferior.
Cuando un módulo es convocado, recibe como entrada los datos proporcionados por otro del mismo o superior nivel, el que ha hecho la llamada; luego realiza su tarea. A su vez este módulo convocado puede llamar a otro u otros módulos de nivel inferior si fuera necesario; cuando ellos finalizan sus tareas, devuelven la salida pertinente al módulo inmediato llamador, en secuencia reversa. Finalmente se continúa con la ejecución del módulo principal. Wikipedia
Las claves
- Los modulos son "pequeños" fragmentos de codigo muy especializados
- Los modulos son "independientes" y remplazables por otros
- Los modulos tienen dependencias entre si, en ocasiones cruzadas
- Los modulos premiten encapsular mucha lógica en forma de cajas negras
- Una buena modularizacón permite subir el nivel de abstraccion de una aplicación
- Existen muchas formas de gestionar la carga y dependencia de modulos en JavaScript
Las ventajas
- Desacoplamineto, que nos ayudará con las refactorizaciones futuras
- Extensibildiad del código
- Alta reutilización del código
- División clara del código, lo que facilita la escalabildiad y el mantenimiento
- Es más sencillo que otros sistemas de gestión de dependencias
- Facilemnte automatizable, después de aprender un nuevo workflow
Las desventajas
- Necesitamos dependencias nuevas para gestioanr esto
- Necesitamos pensar de forma modular, lo que implica más código y artilugios
Recursos
- Ebook | Mastering Modular JavaScript by Nicolás Bevacqua
- Ebook | Exploring JS | Chapter 16: Modules
- Basics of Modular JavaScript
- Everything I know about writing modular JavaScript applications
- Circular dependencies in JavaScript a.k.a. Coding is not a rock-paper-scissors game
- JavaScript Modules: A Beginner’s Guide
- Writing Modular JavaScript With AMD, CommonJS & ES Harmony
- Understanding JavaScript Modules
- Mastering the Module Pattern
- Writing Modular JavaScript
- Copying Objects in JavaScript
- A 10 minute primer to JavaScript modules, module formats, module loaders and module bundlers
- How Do You Structure JavaScript? The Module Pattern Edition
- Code in JavaScript the smart, modular way
- Dependency injection in JavaScript
- Clonando objetos en JavaScript
¿Te suena esto...?
<script src="myjavascript1.js"></script>
<script src="myjavascript2.js"></script>
<!-- ... -->
<script src="myjavascript10.js"></script>
index.html
static/
js/
app/
vendor/
css/
app/
vendor/
Problemas
- Interdependecias horibles
- Mucho, mucho, mucho... ruido en cada fichero
- Demasiado código para leer y entender que pasa
- Demasiada interconexión, imposible refacorizar
- Un cambio mediano puede significar volver a empezar el proyecto de cero
- No es posible reutilizar nada
- Nuestros datos y lógica de negocio se extienden globlamente por todas partes
- Lentitud de carga en toda la web, demasiadas cosas sincronas tienen que cargar
Usemos lo que conocemos
- Namespace Pattern
- Revealing Module Pattern
- Module Pattern
- Singleton Pattern
Limitaciones para la implementación directa
- Poca abstracción en la aplicación
- Mucho código para gestionar cada módulo
- Gestión muy manual y rigida
- La carga asincrona es un reto técnico
- Las dependencias circulares son un tema delicado de gestionar
¿Cómo funciona?
- Fichero
math.js
//@see: http://www.commonjs.org/specs/modules/1.0/
exports.add = function() {
var sum = 0, i = 0, args = arguments, l = args.length;
while (i < l) {
sum += args[i++];
}
return sum;
};
//ES6 Version
exports.add = function(...numbers) {
return numbers.reduce((previous, current) => {
return previous + current;
});
};
- Fichero
increment.js
//@see: http://www.commonjs.org/specs/modules/1.0/
var add = require('math').add;
exports.increment = function(val) {
return add(val, 1);
};
- Fichero
program.js
//@see: http://www.commonjs.org/specs/modules/1.0/
var inc = require('increment').increment;
var a = 1;
inc(a); // 2
Las claves
- Más extendido en el backend, especialmente Node
- Simple y sencillo, no requiere una curva de aprendizaje
- Soporta dependencias circulares
- Una espeficicación sencilla
Los problemas
- Un archivo por modulo
- La carga por definición es sincrona, pero existen workarounds (Ajax)
- No se puede implementar sin una librería
Librerias Específicas
Recursos
- CommonJS effort sets JavaScript on path for world domination
- CommonJS | Web Site
- CommonJS | Spec 1.0
- Thoughts on CommonJS / RequireJS
- CommonJS: Why and How
- AMD is Not the Answer
- Lazy evaluation of CommonJS modules
- JavaScript Growing Up
- The RequireJS Notes On CommonJS
- Taking Baby Steps With Node.js And CommonJS - Creating Custom Modules
- Asynchronous CommonJS Modules for the Browser
- The CommonJS Mailing List
¿Cómo funciona?
- Fichero
math.js
define("add", function () {
var sum = 0, i = 0, args = arguments, l = args.length;
while (i < l) {
sum += args[i++];
}
return sum;
});
//ES6 Version
define("add", (...numbers) => {
return numbers.reduce((previous, current) => {
return previous + current;
});
};
- Fichero
increment.js
define("increment", ["add"], function (add) {
return add(val, 1);
});
- Fichero
program.js
require(['increment'], function ( inc ) {
var a = 1;
inc(a); // 2
});
Las claves
- Historicamente más extendido en el frontend, Angula 1.x, Backbone, etc...
- Creado por developers decontentos con Common.js y su carga síncrona
- Soporta Plugins
- Los modulos pueden romperse en multiples ficheros
- Asincronia de base
- Compatibildiad con
require
yexports
- Buen soporte a dependencias circulares
Los problemas
- Demasiado verboso, mucha decoración...
- No funciona sin librerias externas
Librerías
Recursos
- The RequireJS Guide To AMD
- What's the fastest way to load AMD modules?
- AMD vs. CJS, what's the better format?
- AMD Is Better For The Web Than CommonJS Modules
- AMD No Longer A CommonJS Specification
- On Inventing JavaScript Module Formats And Script Loaders
- The AMD Mailing List
¿Cómo sería?
- Fichero
math.js
export function add(...numbers) {
return numbers.reduce((previous, current) => {
return previous + current;
});
}
- Fichero
increment.js
import {add} from 'math';
export function increment(val) {
return add(val, 1);
}
- Fichero
program.js
import {increment as inc} from 'increment';
const a = 1;
inc(a); // 2
🎁 Bonus: Importación asíncrona
- Asíncrona (solo un módulo)
System.import('modulo')
.then(modulo => {
// Uso del módulo importado
})
.catch(error => {
// Gestión de errores
});
- Asíncrona (multiples módulos)
Promise.all(
['module1', 'module2', 'module3']
.map(x => System.import(x)))
.then(([module1, module2, module3]) => {
// Use module1, module2, module3
});
Las claves
- Sencillo, sencillo, sencillo y mucho más simple
- asincrono y sincrono de igual forma
- Ya viene de fabrica con ES6
- Soporta dependencias circulares
Los problemas
- Soporte :-(
- Tenemos que usar babel para transpilarlo
El problema
- Tenemos demasiados ficheros con muchas dependencias cruzadas
- A veces la carga es muy lenta y compleja
- Tenemos que descargar muchos ficheros distintos en tiempo de ejecucion
- Tenemos limites en cuanto al volumen de ficheros que podemos descargar
- HTTP1.1 tiene muchas limitaciones que rompera http2.0, pero aún no es standard
La idea
- Fusionamos los ficheros del mismo tipo en uno solo
- Servimos un único fichero por tipo
- Hacemos una versión smart, quitando todo lo que nos sobra o no se ejecutará
- Minificamos y comprimimos la información todo lo posible
BOWER is dead. Only use it for legacy reasons.
Literatura al respecto
- Bower/bower #2298: Consider deprecating Bower
- Bower is dead, long live npm. And Yarn. And webpack in snyk.io/blog by Assaf Hefetz
- Reddit: Is bower dead? Should we start using other dependency managers?
- Quora: Is Bower dying?
Paso 1: Tus herramientas son las de siempre
Paso 2: Pasar del bower.json
al package.json
Paso 3: Deja de usar wiredep
y empieza a usar solamente require()
Nota: Esto es al estilo Browserify exclusivamente
- wiredep: Wire Bower dependencies to your source code
- browserify: browser-side require() the node.js way
- Debes instalar browserify de forma global con
npm install -g browserify
- Ejemplo de carga de dependencias
var $ = require('jquery'), _ = require('lodash'), angular = require('angular'); //..
- Puedes comprimir y unificar todo el JS en un solo comando
browserify app.js -o bundle.js
y en un único fichero<script src="bundle.js"></script>
- Si ademas instalas watchify con
watchify app.js -o bundle.js
refescas los cambios en tiempo real.
Extra 1: Cosas Interesantes
Extra 2: Un mundo de información te espera
- Why We Should Stop Using Bower – And How to Do It
- Browserify VS Webpack - JS Drama
- npm and front-end packaging
- The jQuery Module Fallacy
Caracteristicas
- solo soporta el patrón CommonJS, usaremos
require()
en el browser - Genera un fichero bundle con todo el js
- Se puede usar con un fichero de configuración complejo
- Se instala globalmente
npm install browserify -g
Recursos
- browserify-handbook
- Getting Started with Browserify
- Primeros pasos con Browserify con Nazarí González
- Browserify. Desarrollando tu Frontend como en Node.js con Carlos Azaustre
- Getting Started with Browserify with Kyle
- Usa módulos del lado cliente con Browserify! en Cristalab
- Building angular app using browserify in Hackernoon
- Hacking Browserify with Krasimir
Ejemplo
Nuestro fichero public/main.js
:
var foo = require('./foo.js');
var bar = require('../lib/bar.js');
var gamma = require('gamma');
var elem = document.getElementById('result');
var x = foo(100) + bar('baz');
elem.textContent = gamma(x);
Nuestro fichero public/foo.js
:
module.exports = function (n) { return n * 111 }
Comando que hace toda la magia:
browserify main.js > bundle.js
Nuestro fichero public/index.html
<!doctype html>
<html>
<head>
<title>Browserify Playground</title>
</head>
<body>
<script src="/static/js/bundle.js"></script>
</body>
</html>
Documentación
- Web oficial
- Punto de partida
- Descargar
- API
- Optimization
- CommonJS Notes
- FAQs
- Common Errors
- Writing Plugins
- Why Web Modules
- Why AMD
- Requirements
- History
- Get Help
- Blog
- @jrburke en Twitter
- GitHub
- Contributing
Recursos
- Qué es y como funciona RequireJS
- Introducción a Require.js
- Gist | Can you help me understand the benefit of require.js?
- RequireJS Tutorial
- Understanding RequireJS for Effective JavaScript Module Loading
- Introduction to RequireJS
- RequireJS API
- Understanding AMD & RequireJS
- Organize Your Code with RequireJS
- Introducción y ejemplo de RequireJS
- Ejemplos RequireJS
- CommonJS vs AMD vs ES6
- Ejemplo + teoría RequireJS
- Optimizando scripts con RequireJS
Código convencional (Código espagueti)
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Testing Requirejs</title>
</head>
<body>
<script src="calculadora.js"></script>
</body>
</html>
calculadora.js
function sumar (x, y) {
return x+y
};
function restar (x, y) {
return x-y
};
function multiplicar (x, y) {
return x*y
};
function dividir (x, y) {
return x/y
};
Solución sencilla: Creando un objeto
calculadora.js
var calculadora = {};
calculadora.sumar = function (x,y) {
return x + y
};
calculadora.restar = function (x, y) {
return x - y
}
calculadora.multiplicar = function (x, y) {
return x * y
}
calculadora.divir = function (x, y) {
return x / y
}
Solución modular: Namespace Pattern
index.html
<script src="sumar.js"></script>
<script src="restar.js"></script>
<script src="divir.js"></script>
<script src="multiplicar.js"></script>
sumar.js
var calculadora = calculadora || {};
calculadora.sumar = function (x,y) {
return x + y
};
restar.js
var calculadora = calculadora || {};
calculadora.restar = function (x, y) {
return x - y
}
dividir.js
var calculadora = calculadora || {};
calculadora.divir = function (x, y) {
return x / y
}
multiplicar.js
var calculadora = calculadora || {};
calculadora.multiplicar = function (x, y) {
return x * y
}
Usando Require.js
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Testing Requirejs</title>
</head>
<body>
<script data-main="calculadora" src="require.js"></script>
</body>
</html>
calculadora.js
require(['calculadora/sumar', 'calculadora/restar', 'calculadora/cuadrado'], function (sum, res, cua) {
console.info(sum(1,2)); // 1 + 2
console.info(res(3,1)); // 3 - 1
console.log(cua(2)); // 2 * 2
});
calculadora/sumar.js
define(function () {
return function (x, y) {
return x + y;
};
});
calculadora/restar.js
define(function () {
return function (x, y) {
return x - y;
};
});
calculadora/multiplicar.js
define(function () {
return function (x, y) {
return x * y;
};
});
calculadora/cuadrado.js
define(['calculadora/multiplicar'], function (multiplicar) {
return function (x) {
return multiplicar(x, x);
};
});
Require.js con dependencias externas
// vendor/jquery.min.js
require(['vendor/jquery'], function($){
$('#hola').html("<h1>HOLA! Hola!</h1>");
});
Generando varios modulos en el mismo archivo
script.js
require(['hola', 'adios'], function(hola, adios){
$('#hola').html("<h1>"+hola+" y "+adios+"!</h1>");
});
hola.js
define(function() {
return "Hola";
});
define('adios', function() {
return "adios";
});
configurando baseUrl
Estructura del proyecto:
www/
/assets/
/css/
/js/
/App/
main.js
/vendor/
bootstrap.js
jquery.js
config.js
requirejs.config({
baseUrl: '.assets/js'
});
*.js
require(['vendor/jquery', 'vendor/bootstrap', 'app/main']);
configurando Paths
Estructura del proyecto:
www/
/assets/
/css/
/js/
/app/
main.js
/vendor/
bootstrap.js
jquery.js
config.js
requirejs.config({
baseUrl: '.assets/js',
paths: {
'jquery': 'vendor/jquery',
'bootstrap': 'vendor/bootstrap',
'main': 'app/main'
}
});
*.js
require(['jquery', 'bootstrap', 'main']);
1 - Modulariza tu proyecto usando algún sistema de gestión de dependencias (AMD, Common, ES6)
// Tu solución