Introducción a Angular JS

Deployando a producción Más videos

Descripción del tema

Ya tenemos nuestro app completa, ahora vamos a prepararla para deployarla a producción, para eso necesitamos ofuscar y comprimir nuestro código JavaScript, así como el CSS.

Para comprimir el código usaremos Grunt, ya he escrito un tutorial de como instalarlo, así que te recomiendo hacerlo en este momento antes de seguir adelante. Una vez que tenemos instalado Grunt necesitamos instalar dos plugins.  

  1. grunt-contrib-concat, para concatenar nuestro código fuente en un solo archivo.
  2. grunt-contrib-cssmin, para comprimir el CSS.
  3. grunt-contrib-uglify, para ofuscar y comprimir el JavaScript.
  4. grunt-processhtml, para remplazar los archivos de producción en el index.html.

Para instalar estas dos plugins usamos NPM.

$ npm install grunt-contrib-concat grunt-contrib-cssmin grunt-contrib-uglify grunt-processhtml

Con esto tendremos lo necesario para seguir adelante. Vamos a ir creando las tareas paso a paso para ir explicando cada configuración a detalle.

Concatenar los archivos

Actualmente tenemos dos archivos CSS y dos archivos JS, antes de comprimir y ofuscar el código necesitamos concatenar todos los archivos en uno solo, para eso usamos el primer plugin que instalamos. Vamos a crear el archivo Gruntfile.js en el directorio raíz de nuestra app, luego agregamos el siguiente código.

module.exports = function(grunt) {

  grunt.initConfig({
    concat: {           //Step 1
      css: {            //Step 2
        src: [
          'css/*'
        ],
        dest: 'build/css/app.css'
      },
      js : {
        src : [
          'js/bootstrap-select.js',
          'js/app.js'
        ],
        dest : 'build/js/app.js'
      }
    }
  });

  // Load the plugins
  grunt.loadNpmTasks('grunt-contrib-concat'); //Step 3

  // Default task(s).
  grunt.registerTask('default', ['concat:css','concat:js']);  //Step 4
};
  1. En el primer paso definimos las configuraciones de cada tarea, en este caso configuramos el plugin concat creando dos subtareas, una para concatenar los archivos CSS y otra para el JavaScript. 
  2. Dos configuraciones son necesarias para cada subtarea, indicar donde está el código fuente (src) a concatenar y el nombre del archivo donde pondrá el resultado de la concatenación (dest). 
  3. En el paso tres cargamos el plugin que nos hará la concatenación.
  4. En el paso cuatro definimos la tarea que se ejecutará por default, es importante notar que estamos indicando el order de las tareas a ejecutar mediante el arreglo del segundo parámetro.

Una vez que configuramos estas dos tareas podemos ejecutar Grunt de la siguiente manera. El siguiente comando debe ser ejecutado en el directorio raíz de nuestra app.

$ grunt

Ya que no le estamos indicando que tarea ejecutar, por defecto ejecutará la tarea default. Cuando termina la ejecución podremos ver que se han creados los dos archivos correctamente, uno para el CSS y otro para el JS, todo dentro del folder build.

Comprimir el CSS

Lo siguiente es tomar el resultado obtenido en la tarea anterior y comprimirlo con el segundo plugin que instalamos al inicio del tutorial.

Vamos a agregar las siguiente configuraciones al archivo Gruntfile.js.

module.exports = function(grunt) {

  grunt.initConfig({
    
    // ...

    cssmin : {                                //Step 1
        css:{
            src: 'build/css/app.css',
            dest: 'build/css/app.min.css'
        }
    },
  });

  // Load the plugins
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-cssmin');  //Step 2

  // Default task(s).
  grunt.registerTask('default', ['concat:css','concat:js','cssmin:css']); //Step 3

};
  1. En el paso uno definimos las configuraciones para la tarea cssmin, unicamente es necesario indicar el archivo fuente, que en este caso es el resultado de la tarea anterior y el archivo destino donde se guardará el código comprimido.
  2. En el paso dos cargamos el plugin que realizará la compresión.
  3. En el paso tres agregamos la nueva tarea a la tarea default.

Al ejecutar nuevamente Grunt desde la consola, veremos que se ha comprimido el CSS de 24.35KB a 7.67KB, esto es genial! Ya tenemos listo nuestro CSS para deployarlo a producción.

Comprimir CSS con Grunt

Comprimir CSS con Grunt

Comprimir el JavaScript

Ahora usaremos uglify para comprimir el JavaScript, vamos a configurar la tarea de la siguiente manera.

module.exports = function(grunt) {

  grunt.initConfig({

    //...

    uglify: {     //Step 1
        js: {
            files: {
                'build/js/app.min.js': ['build/js/app.js']
            }
        }
    }
  });

  // Load the plugins
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-contrib-uglify');  //Step 2

  // Default task(s).
  grunt.registerTask('default', ['concat:css','concat:js','cssmin:css','uglify:js']); //Step 3

};
  1. En el paso uno configuramos el archivo a comprimir, dentro de la propiedad files indicamos el archivo donde pondrá el código ofuscado y comprimido, así como el archivo que procesará.
  2. En el paso dos agregamos el plugin que hará la compresión.
  3. En el paso tres agregamos la nueva tarea a la tarea default.

Si ejecutamos Grunt nuevamente, veremos que se ha creado un archivo con el código JavaScript ofuscado y comprimido, el archivo original pesaba 49kb y el compreso pesa solo 25kb. Ya tenemos el JavaScript listo para subir a producción.

Cambiar los archivos de producción en el HTML

Ya que tenemos listo el CSS y el JavaScript para subirlo a producción, necesitamos preparar el index.html para que ahora llame los archivos de producción en lugar de los de desarrollo, para eso vamos a utilizar el plugin grunt-processhtml que instalamos al inicio de este tutorial.

Vamos a modificar el archivo Gruntfile.js agregando el siguiente código.

module.exports = function(grunt) {

  grunt.initConfig({
    
    //...

    processhtml : {   //Step 1
      dist: {
        files : {
          'build/index.html' : 'index.html'
        }
      }
    }
  });

  // Load the plugins
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-processhtml'); //Step 2

  // Default task(s).
  grunt.registerTask('default', [
    'concat:css',
    'concat:js',
    'cssmin:css',
    'uglify:js',
    'processhtml:dist' //Step 3
  ]);

};
  1. En el paso uno agregamos las configuraciones de la tarea, aquí solamente definimos el archivo que modificaremos y el nombre del archivo que resultará, usamos la propiedad files.
  2. En el paso dos incluimos el plugin que realizará el procesamiento necesario.
  3. Por último agregamos la nueva tarea a las dependencias de la tarea default.

Antes de ejecutar la nueva tarea, necesitamos modificar el index.html de la siguiente manera.

<!DOCTYPE html>
<html ng-app="Bookmarks">
<head>
    <title>Installation - Angular JS</title>
    <meta charset="utf-8"> 

    <!-- Latest compiled and minified CSS -->
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">

    <!-- build:css css/app.min.css -->
    <link rel="stylesheet" type="text/css" href="css/bootstrap-select.css">
    <link rel="stylesheet" type="text/css" href="css/style.css">
    <!-- /build -->
</head>
<body ng-controller="MainController">

    //...


    <!-- Latest compiled and minified JavaScript -->
    <script type="text/javascript" src="//code.jquery.com/jquery-2.1.1.min.js"></script>
    <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular.min.js"></script>
    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular-resource.min.js"></script>
    
    <!-- build:js js/app.min.js -->
    <script src="js/bootstrap-select.js"></script>
    <script type="text/javascript" src="js/app.js"></script>
    <!-- /build -->
</body>
</html>

Aquí estamos agregando unos comentarios a las hojas de estilos y a los scripts que incluimos, esto porque en desarrollo si vamos a cargar todos y cada uno de los archivos, pero en producción cargaremos los comprimidos. 

Para las hojas de estilos utilizamos este comentario: <!-- build:css css/app.min.css -->, con esto le indicamos al plugin que procesará este documento, que remplezarémos desde el inicio de este comentario hasta que encuentre este otro comentario: <!-- /build -->.

Todo lo que esté dentro de esos dos comentario será remplazado por el archivo que le indicamos en el comentario, en este caso le indicamos que será una hoja de estilos css y le pasamos la ruta del archivo que tiene que incluir. Esto nos permitirá generar una versión del index.html para producción.

Lo mismo hacemos para el javascript, algo interesante que notar es que no estamos incluyendo en el bloque las dependencias de Angular, jQuery y bootstrap, esto es porque estamos importando estos archivos desde el CDN. Si usáramos Bower o tuviéramos esas librerías en nuestro proyecto entonces podríamos comprimirlas y generar un único archivo.

Una vez que agregamos esos comentarios en el HTML podemos correr las tareas y ver que el html se está generando correctamente, incluyendo las versiones de producción! Lo mejor de todo es que hemos automatizado el proceso usando Grunt!

Consideraciones al comprimir Angular

Ahora bien, si en este momento probamos la versión de producción, veremos que no funciona correctamente. Sucede que las dependencias no se están inyectando correctamente, esto es porque cuando el JavaScript se comprime el nombre de las variables cambia, así como el de los parámetros. Por lo tanto Angular no puede encontrar correctamente los servicios, directivas ni nada de lo que hicimos.

Para solucionar esto debemos indicarle el nombre de cada parámetro a inyectar mediante un string, vamos a modificar todos nuestros componentes de la siguiente manera.

(function(){
    "use strict";

    angular.module('Bookmarks',[
        //dependencies here
        'ngResource'
    ])

    .service('Category',['$http',function($http){ //<---

        //...
    }])

    .factory('Bookmark',['$resource',function($resource){ //<---

        //...

    }])

    .directive('bootstrapSelect',['$parse',function($parse){ //<---

        //...

    }])

    .controller('MainController',['$scope','Category','Bookmark',function($scope,Category,Bookmark){ //<---
        
        //...

    }]);

})();

Ahora para el segundo parámetro de cada componente le estamos enviando un arreglo, este arreglo contiene las dependencias que usaremos y por último la función que creará cada componente. Algo importante por mencionar es que tanto los strings como los parámetros de la función deben estar en el mismo orden.

Con esto es suficiente para que Angular pueda hacer el dependency injection aún cuando el código este ofuscado y comprimido, si ejecutamos nuevamente las tareas de deployment con Grunt podremos ver la app funcionando correctamente. 

Deployando Angular JS

Deployando Anguar JS

Conclusiones

Todo esto podría parecer complicado e intimidante al principio, pero realmente que el uso de Grunt nos ayudará mucho en la productividad, ya que con un simple comando podemos ejecutar todas estas tareas que de otra forma tendríamos que hacerlo de manera manual.

Mi recomendación es que desde un principio prepares tu app pensando en como vas a deployarla, agregues las dependencias a Angular usando el arreglo que vimos, yo decidí hacerlo al último por que es una app pequeña y quería mostrar el error que apareció.

Hoy en día tenemos muchas herramientas, este proceso que acabo de mostrar recuerdo hacerlo allá por el 2008 de manera manual, usando algunas herramientas caseras con mi equipo de desarrollo, realmente que era complicado y tedioso hacer los deployments, hoy Grunt nos facilita la vida.

Si te ha gustado este tutorial recuerda compartirlo en tus redes sociales eso me ayudará a seguir publicando contenidos de calidad.

Te gustaría recibir más tutoriales como este en tu correo?

Este tutorial pertenece al curso Introducción a Angular JS, te recomiendo revises el resto de los tutoriales ya que están en secuencia de menor a mayor complejidad.

Si deseas recibir más tutoriales como este en tu correo te recomiendo registrarte al curso, si ya eres miembro solo identifícate y registrate al curso, si no eres miembro te puedes registrar gratuitamente!

Si no gustas registrarte en este momento no es necesario! Aún así puedes recibir los nuevos tutoriales en tu correo! Jamás te enviaremos Spam y puedes cancelar tu suscripción en cualquier momento.

¿Olvidaste tu contraseña?

9Comentarios

  • Avatar-7 Anibal Ardid 01/07/2015

    Espectacular la explicacion. 2 consultas : 1- tengo las inyecciones hechas como mencionas ahi, por ejemplo: angular.module('adminApp') .config(['$routeProvider', '$httpProvider', function ($routeProvider, $httpProvider) { ......................... angular.module('adminApp') .controller('HomeCtrl', ['$scope', '$location', '$localStorage', 'Main', function($scope, $location, $localStorage, Main) { ............. Pero al ingresar por browser me da el siguiente error en consola: Error: [$injector:modulerr] http://errors.angularjs.org/1.2.20/$injector/modulerr?p0=adminApp&p1=%5B%24injector%3Amodulerr%5D%20http%3A%2F%2Ferrors.angularjs.org%2F1.2.20%2F%24injector%2Fmodulerr%3Fp0%3DngStorage%26p1.........................

    • Avatar-6 Crysfel Villa 01/07/2015

      Revisa que tengas incluida el ngStorage, me parece que ese es el error, al menos esa es la dependencia que dice el mensaje. Ya incluiste el módulo?

    • Avatar-6 Anibal Ardid 01/07/2015

      Y la 2da consulta seria: - Como se puede configurar un "watcher" para q me levante en el browser mi proyecto y con cada cambio que le haga me refresque la pagina ?

    • Avatar-11 Anibal Ardid 01/07/2015

      Y otra consulta mas ... En mi aplicacion tengo la carpeta de bower_components de la cual llamo desde mi app. al usar grunt me gustaria concatenar todos esos "vendor" en un unico archivo o sino pasar solo los q se usan (por ejemplo los .min.css y .min.js) al build .

      • Avatar-5 Crysfel Villa 01/07/2015

        Lo que generalmente hago yo es crear un arreglo con todas las dependencias que necesito (esas que están en bower), luego en el build incluyo ese array a la concatenación junto con el código fuente, solo asegúrate que se incluyan antes que tu app. Una vez concatenados se comprimen.

        • Avatar-1 Anibal Ardid 01/07/2015

          groso ! ahi lo vi en el siguiente tutorial ;)

        • Avatar-10 martin urciuoli 19/07/2017

          Hola! Como podria agregar videos .mov o archivos .pdf con el grunt. Ya que me genera el build pero sin esos archivos! Desde ya, muchas gracias!!

          Instructor del curso

          Crysfel3

          Autor: Crysfel Villa

          Ha participado en varios proyectos con Angular y es participante activo del grupo de Angular NYC.

          Descarga video Descarga Código Fuente Ver Demostración

          Regístrate a este curso

          Este tutorial pertenece al curso Introducción a Angular JS, revisa todos los tutoriales que tenemos en este mismo curso ya que están en secuencia y van de lo más sencillo a lo más complicado.

          Tendrás acceso a descargar los videos, códigos y material adicional.

          Podrás resolver los ejercicios incluidos en el curso así como los Quizzes.

          Llevarás un registro de tu avance.