Transpiling de ECMAScript 6 a 5, Uglify y ESLint con Gulp

Escrito por el , actualizado el .
javascript planeta-codigo programacion
Enlace permanente Comentarios

JavaScript

Este artículo es una combinación de varias cosas que forman el actual estado del arte en JavaScript. Gulp como ejecutor de tareas, traducción o transpiling de ECMAScript 6 a ECMAcript 5 con Babel, Uglify como minificador y ofuscador de código y ESLint como verificador del código.

Dos de los navegadores web más importantes como Chrome y Firefox ya soportan gran parte de la especificación de ECMAScript 6 que añade nuevas funcionalidades al lenguaje JavaScript que comento en Introducción al JavaScript de ECMAScript 6. Sin embargo, hasta que prácticamente toda la totalidad de dispositivos soporten ECMAScript 6, formada por la diversidad actual de dispositivos en las que se incluyen los dispositivos móviles es necesario traducir el código JavaScript a la especificación ECMAScript 5. Haciendo esta traducción podemos usar ES6 y al mismo tiempo soportar todos los dispositivos. Hacer transpiling es necesario si el código será accedido desde internet de forma pública para cualquiera y queremos usar las nuevas características de ECMAScript 6. Si se tratase de una aplicación en un entorno controlado de uso interno en el que se usasen los navegadores que soporten ES6 el transpiling no sería imprescindible.

El tamaño de una página afecta al tiempo de carga de la misma, dada las velocidades de incluso los dispositivos móviles la velocidad ya no es tan importante como cuando el ancho de banda era significativamente menor y ahora importa más la latencia que el protocolo HTTP/2 trata de mejorar. En cualquier caso tanto comprimir con gzip el contenido devuelto por el servidor como minificar se consigue la mayor reducción en el tamaño de una página.

Minificar el código y ofuscarlo hace que depurar sea más complicado al no tener el código fuente original. Para que los depuradores tengan la información de los nombres originales al minificar se pueden generar archivos .map que los contengan y que los depuradores obtienen y usan cuando es necesario.

ESLint es un verificador estático de código que nos informará de aquellas reglas que se no se respetan según las normas de estilos que definamos. En ESLint hay cantidad de reglas que podemos aplicar al código y personalizar según nuestras preferencias, por ejemplo, requerir que las cadenas de texto se usen mediante comillas simples o que las llaves de bloques de código estén en la misma linea que la expresión if, else, while, etc.

Gulp define las tareas de un proyecto en un archivo de nombre gulpfile.js que usando gulp-balbel, eslint, gulp-uglify y gulp-sourcemaps obtenemos todas las funcionalidades anteriores. Con Gulp los archivos son transformados en varios pasos y finalmente enviados a un directorio destino del sistema de archivos.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
const gulp = require('gulp');
const babel = require('gulp-babel');
const eslint = require('gulp-eslint');
const uglify = require('gulp-uglify');
const sourcemaps = require('gulp-sourcemaps');

gulp.task('default', function() {
	gulp.src("src/main/js/**/*.js")
		.pipe(eslint())
		.pipe(eslint.format())
		.pipe(babel())
		.pipe(gulp.dest("build/dist/babel"))
		.pipe(sourcemaps.init({loadMaps: true}))
		.pipe(uglify())
		.pipe(gulp.dest("build/dist/uglify"))
		.pipe(sourcemaps.write('./'))
		.pipe(gulp.dest("build/dist"));
});
gulpfile.js

Para usar Gulp deberemos tener instalado npm que en Arch Linux sería instalar su paquete:

1
2
# pacman -S npm

npm.sh

Instalar Gulp a nivel global en el sistema:

1
2
# npm install -g gulp

gulp.sh

Y finalmente instalar en el proyecto las dependencias definidas en el archivo package.json:

1
2
3
4
$ npm install --save-dev gulp-babel
$ npm install --save-dev gulp-eslint
$ npm install --save-dev gulp-uglify gulp-sourcemaps
$ npm install
npm-install.sh

El archivo JavaScript original con ECMAScript 6 es el siguiente.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
'use strict';
const sentences = [
    { subject: 'JavaScript', verb: 'is', object: 'great' },
    { subject: 'Elephants', verb: 'are', object: 'large' }
];

function say ({ subject, verb, object }) {
    console.log(`${subject} ${verb} ${object}`);
}

for (let s of sentences) {
    say(s);
}
main.js

Transformado a ECMAScript 5 queda lo siguiente.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
'use strict';

var sentences = [{ subject: 'JavaScript', verb: 'is', object: 'great' }, { subject: 'Elephants', verb: 'are', object: 'large' }];

function say(_ref) {
    var subject = _ref.subject;
    var verb = _ref.verb;
    var object = _ref.object;

    console.log(subject + ' ' + verb + ' ' + object);
}

var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;

try {
    for (var _iterator = sentences[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
        var s = _step.value;

        say(s);
    }
} catch (err) {
    _didIteratorError = true;
    _iteratorError = err;
} finally {
    try {
        if (!_iteratorNormalCompletion && _iterator.return) {
            _iterator.return();
        }
    } finally {
        if (_didIteratorError) {
            throw _iteratorError;
        }
    }
}
main-babel.js

Estos son los archivos de configuración para ESLint donde indicaremos las reglas que queremos aplicar al código JavaScript y la configuración para Babel donde indicamos a que versión de JavaScript haremos la transformación.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
module.exports = {
    "extends": "standard",
    "plugins": [
        "standard",
        "promise"
    ],
    "rules": {
        "semi": ["error", "always"],
        "indent": ["error", 4]
    }
};
eslintrc.js
1
2
3
{
  "presets": ["es2015"]
}
babelrc

Transformado con Uglify queda algo ilegible pero con el archivo .map podremos depurar en el navegador mostrándose los nombres de las variables originales.

1
2
"use strict";function say(r){var t=r.subject,e=r.verb,o=r.object;console.log(t+" "+e+" "+o)}var sentences=[{subject:"JavaScript",verb:"is",object:"great"},{subject:"Elephants",verb:"are",object:"large"}],_iteratorNormalCompletion=!0,_didIteratorError=!1,_iteratorError=void 0;try{for(var _iterator=sentences[Symbol.iterator](),_step;!(_iteratorNormalCompletion=(_step=_iterator.next()).done);_iteratorNormalCompletion=!0){var s=_step.value;say(s)}}catch(r){_didIteratorError=!0,_iteratorError=r}finally{try{!_iteratorNormalCompletion&&_iterator.return&&_iterator.return()}finally{if(_didIteratorError)throw _iteratorError}}

main-uglify.js
1
2
{"version":3,"sources":["main.js"],"names":["say","_ref","subject","verb","object","console","log","sentences","_iteratorNormalCompletion","_didIteratorError","_iteratorError","undefined","_iterator","Symbol","iterator","_step","next","done","s","value","err","return"],"mappings":"AAAA,YAIA,SAASA,KAAIC,GACT,GAAIC,GAAUD,EAAKC,QACfC,EAAOF,EAAKE,KACZC,EAASH,EAAKG,MAElBC,SAAQC,IAAIJ,EAAU,IAAMC,EAAO,IAAMC,GAP7C,GAAIG,aAAeL,QAAS,aAAcC,KAAM,KAAMC,OAAQ,UAAaF,QAAS,YAAaC,KAAM,MAAOC,OAAQ,UAUlHI,2BAA4B,EAC5BC,mBAAoB,EACpBC,eAAiBC,MAErB,KACI,IAAK,GAAIC,WAAYL,UAAUM,OAAOC,YAAaC,QAASP,2BAA6BO,MAAQH,UAAUI,QAAQC,MAAOT,2BAA4B,EAAM,CACxJ,GAAIU,GAAIH,MAAMI,KAEdnB,KAAIkB,IAEV,MAAOE,GACLX,mBAAoB,EACpBC,eAAiBU,EACnB,QACE,KACSZ,2BAA6BI,UAAUS,QACxCT,UAAUS,SAEhB,QACE,GAAIZ,kBACA,KAAMC","file":"main.js","sourcesContent":["'use strict';\n\nvar sentences = [{ subject: 'JavaScript', verb: 'is', object: 'great' }, { subject: 'Elephants', verb: 'are', object: 'large' }];\n\nfunction say(_ref) {\n    var subject = _ref.subject;\n    var verb = _ref.verb;\n    var object = _ref.object;\n\n    console.log(subject + ' ' + verb + ' ' + object);\n}\n\nvar _iteratorNormalCompletion = true;\nvar _didIteratorError = false;\nvar _iteratorError = undefined;\n\ntry {\n    for (var _iterator = sentences[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {\n        var s = _step.value;\n\n        say(s);\n    }\n} catch (err) {\n    _didIteratorError = true;\n    _iteratorError = err;\n} finally {\n    try {\n        if (!_iteratorNormalCompletion && _iterator.return) {\n            _iterator.return();\n        }\n    } finally {\n        if (_didIteratorError) {\n            throw _iteratorError;\n        }\n    }\n}"]}

main.js.map
Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
npm install, gulp, node ./build/dist/main.js

El resultado son los siguientes mensajes en la terminal tanto para la ejecución del archivo ECMAScript 6 original con node src/main/js/main.js como para el transpilado con node ./build/dist/main.js.

1
2
JavaScript is great
Elephants are large
System.out


Comparte el artículo: