Iris: Modern front-end development: Iris + GruntJS + NodeJS

[Fuente: http://www.thegameofcode.com/2013/05/front-end-with-iris-nodejs-gruntjs.html]

¿Por qué utilizar Iris?

Los motores de Javascript actuales están evolucionando rápidamente y se están volviendo más rápidos y potentes.

Sin embargo por otro lado es común encontrar códigos fuente de Javascript realmente feos y liosos. El framework de Iris te guía en la buena dirección y te ayuda a programar aplicaciones web complejas:

  • Buena organización del código fuente: All our JS code is split into small files providing scalability & maintainability. Don’t worry about the large number of files created because you can concatenate them using grunt, thus avoiding a large number of HTTP requests.
  • Solución single-page: Navigation is performed without reloading the page using hash-URLs, e.g. gmail. The navigation system is resolved in a simple way using JS, therefore performance is improved.
  • Motor rápido de templates: With Iris templates you can easily add multilanguage texts into screens and UIs using @@ annotations. In the same way you can print formatted strings as currencies, dates, numbers, etc…
  • Muy ligero de peso con lo que se carga rápido: Iris is very light, about 16KB,  and only depends on jQuery. All files are HTML or JS, therefore your front-end code is independent of server technology. This allows Iris components to be cached by the browser.
  • Buena documentación: 

About Iris

Iris te proporciona la estructura las aplicaciones web proporcionando templates , componentes UI , screens and resources.

Todos los paths de los componentes deben ser definidos previamente en  iris.path  ,por ejemplo.: iris.path.ui = “path/to/my/ui.js”

    • Templates are written in HTML files. These are stored in separate *.html files, therefore a layout designer can edit them easily.
<div> <!-- All templates must have a root element -->

    <!-- The (@@...@@) annotations will print translated text -->
    <b>@@name@@</b>:

    <!-- The (##...##) annotations will be replaced by template params -->
    <b>##user.name##</b>

    <!-- The button element can be used by a UI or Screen due the data-id attr -->
    <button data-id="accept_button">@@ok@@</button>
</div>
    • UIs are graphical components written in JS that can be reused. UIs uses a template to add functionality and manage events. These objects are stored in separate *.js files.
iris.ui(

    // "self" is equal to "this" but can be used
    // inside private functions
    function(self) {

        // function called automatically once when a UI
        // of this kind is instanced
        self.create = function() {
            // set the template
            self.tmpl(iris.path.ui.html);

            // return the jq element with data-id=button
            self.get("button").on("click", function (){
                alert( "Hi " + self.setting("name") + "!");
            });
        };

    }
);
      You can use UIs to create an HTML element into a template:

 

<!-- The "name" setting will be "Jonh" -->
<div data-id="my_ui" data-name="Jonh"></div>
      The <div> element will be replaced by the template

template.html

      :
<div>
    <b>UI Example</b>
    <button data-id="button">Say Hi!</button>
</div>
      Finally, you can create a UI instance inside screens or other UIs:

 

self.ui("my_ui", iris.path.ui.js);
      The result:

 

UI Example 
    • Screens are navigable elements, bound to a unique hash fragment (#screen), and are stored in separate *.js files. Screens use a template to add functionality and manage events like UI does. You can also instantiate UI objects within screens. Screens’s declaration are very similar to the UI’s declaration, use iris.screen() instead ofiris.ui()
iris.screen(

    // "self" is equal to "this" but can be used
    // inside private functions
    function(self) {

        // function called automatically once when this screen is instanced
        self.create = function() {
            // set the template
            self.tmpl(iris.path.screen.html);

            // created a UI component, the setting "name" will be "Anonymous"
            self.ui("my_ui", iris.path.ui.js, { name : "Anonymous" });
        };
    }
);
      The content of the screen will be

iris.path.screen.html

      .

 

<div>
    <b>My Screen</b><br />
    <div data-id="my_ui"></div>
</div>
      Finally, you can create this screen navigating to them, use

iris.navigate()

       or type the URL into your browser:
iris.navigate("#/my_screen");

 

      The result is a web page like:

 

My Screen
UI Example 

Grunt advantages

The most important advantage of Grunt is automation. With hundreds of plugins to choose from, you can do repetitive tasks like minification, compilation, unit testing, linting, etc. After you’ve configured it, a task runner can do most of that mundane work for you with a minimum of effort.

To run Grunt you need to install NodeJS, for more information please visit http://gruntjs.com/getting-started.

Complete example

I have written a complete example based on the famous todomvc, using Iris and Grunt.
If you want to learn more about the iris-todomvc please check out this tutorial.

Project structure

Gruntfile.js
All of the configuration for our Grunt build process sits inside our Gruntfile (Gruntfile.js) in our project directory.

module.exports = function(grunt) {

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    jshint: {
      all: ['Gruntfile.js', 'src/app/**/*.js']
    },
    uglify: {
      options: {
        banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
      },
      build: {
        src: '<%= pkg.iris.baseUri %>**/*.js',
        dest: '<%= pkg.iris.init %>'
      }
    },
    connect: {
      dev: {
        options: {
          base: 'src'
        }
      },
      dist: {
        options: {
          base: 'dist'
        }
      }
    },
    watch: {
      scripts: {
        files: ['src/**/*.js'],
        tasks: ['jshint'],
        options: {
          nospawn: true
        }
      }
    },
    copy: {
      main: {
        files: [
          {expand: true, cwd: 'src/', src: ['**'], dest: 'dist/'}
        ]
      }
    },
    clean: ['dist']
  });

  // Custom task to concatenate Iris templates
  grunt.registerTask('iris-tmpl', function() {
    grunt.log.writeln('Concatenate Iris templates...');

    var pkg = grunt.file.readJSON('package.json');
    var irisBaseUri = pkg.iris.baseUri;
    var destFile = pkg.iris.init;
    grunt.log.writeln('Configurations: baseUri['+ irisBaseUri +'] init['+ destFile +']');

    var templates = '';
    grunt.file.expand({cwd : irisBaseUri}, '**/*.html').forEach(function(file){
        var source = grunt.file.read(irisBaseUri + file, {encoding : "utf8"})
                .replace(/[\r\n\t]/g, '').replace(/\"/g, '\\"');
        templates += 'iris.tmpl("' + file + '", "' + source + '");\n';
    });

    var irisTmpl = grunt.file.read(destFile, {encoding : "utf8"}) + templates;
    grunt.file.write(destFile, irisTmpl);
    grunt.log.writeln('File "' + destFile + '" saved.');
  });

  // Load tasks
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-connect');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-clean');

  // Register Grunt tasks
  grunt.registerTask('default', ['jshint', 'clean', 'copy', 'uglify', 'iris-tmpl']);
  grunt.registerTask('devserver', ['jshint', 'connect:dev', 'watch']);
  grunt.registerTask('distserver', ['default', 'connect:dist', 'watch']);

};

package.json
To run this example, first install Nodejs and Grunt, after go to project’s root folder and execute $ npm install then you can run the Grunt tasks.

{
  "name": "todomvc-iris",
  "description": "TodoMVC - Iris",
  "version": "1.0.0",
  "private": true,
  "iris" : {
    "init" : "dist/app/init.js",
    "baseUri" : "src/app/"
  },
  "devDependencies": {
    "grunt": "0.4.1",
    "grunt-contrib-uglify": "0.2.0",
    "grunt-contrib-connect": "0.3.0",
    "grunt-contrib-watch": "0.3.1",
    "grunt-contrib-jshint": "0.4.3",
    "grunt-contrib-concat": "0.2.0",
    "grunt-contrib-copy": "0.4.1",
    "grunt-contrib-clean": "0.4.1"
  }
}

Grunt tasks

  • $ grunt Use this command to build the application.
    $ grunt
    Running "jshint:all" (jshint) task
    >> 5 files lint free.
    
    Running "clean:0" (clean) task
    Cleaning "dist"...OK
    
    Running "copy:main" (copy) task
    Created 4 directories, copied 12 files
    
    Running "uglify:build" (uglify) task
    File "dist/app/init.js" created.
    
    Running "iris-tmpl" task
    Concatenate Iris templates...
    Configurations: baseUri[src/app/] init[dist/app/init.js]
    File "dist/app/init.js" saved.
    
    Done, without errors.

    How it works?

    1. First, Grunt checks code style and validates it using JSHint, if all goes well, it will copy all files from /src to/dist folder.
    2. The next step is to concatenate and compress the source code of our application (src/app/**.js files).
    3. Thereafter, all iris components (screens, UIs & resources) have been concatenated into a single file (dist/app/init.js).
      ...
      uglify: {
            ...
            build: {
              src: '<%= pkg.iris.baseUri %>**/*.js',
              dest: '<%= pkg.iris.init %>'
            }
          },
      ...
    4. Finally, all Iris templates are concatenated using the custom task iris-tmpl. All HTML have been appended to dist/app/init.js.

    You can change this packaging settings in package.json:

    ...
     "iris" : {
        "init" : "dist/app/init.js",
        "baseUri" : "src/app/"
      }
    ...

    All application files are a single file of 5.33 kb. This is perfect for slow connections like 3G networks.

  • $ grunt devserver Use this command to test and develop the web application.
    $ grunt devserver
    Running "jshint:all" (jshint) task
    >> 5 files lint free.
    
    Running "connect:dev" (connect) task
    Started connect web server on localhost:8000.
    
    Running "watch" task
    Waiting...
    1. This command checks code style and validates it using JSHint
    2. It starts a local web server and sets its root folder to /src and waits for file changes. You can test the web in your browser and, at the same time, change the files with some text editor.
  • $ grunt distserver Execute this command to test the build
    $ grunt distserver
    Running "jshint:all" (jshint) task
    >> 5 files lint free.
    
    Running "clean:0" (clean) task
    Cleaning "dist"...OK
    
    Running "copy:main" (copy) task
    Created 4 directories, copied 12 files
    
    Running "uglify:build" (uglify) task
    File "dist/app/init.js" created.
    
    Running "iris-tmpl" task
    Concatenate Iris templates...
    Configurations: baseUri[src/app/] init[dist/app/init.js]
    File "dist/app/init.js" saved.
    
    Running "connect:dist" (connect) task
    Started connect web server on localhost:8000.
    
    Running "watch" task
    Waiting...
    1. Grunt executes the default task (like $ grunt)
    2. It initializes a local server to test the generated website at /dist folder. You can test the final application typing into your browser: http://localhost:8000/

Download the Iris todo-list example and learn how to create your own Iris + Grunt + Nodejs applications.
You can find the demo here.

Enjoy it!