utorok 9. apríla 2013

Mobile webapps with nodejs & typescript - Part II

Overview

In Part I we setup basic project and prepared output package for mobile device. This got us some benefits coming from statically typed system provided by Typescript, dependency loading and given us some automation in building and possible integration with Continuous Integration systems.
This part will cover additional tools that can be used to provide better cooperation with other developers and template engines. We will cover following topics:
  • Templates of view 
  • Templates of CSS styles
  • Maintenance

Jade - view templates

Jade is powerful template engine for HTML pages or snippets that can be controlled via models passed. Traditionally it's more used for web servers to provide base templates however with our setup we can easily reuse this component to prepare mobile web applications and take away some of redundancy coming to play with .
To start with we install Jade task for Grunt:

> npm install grunt-contrib-jade --save-dev

Now we can update our existing views/index.html file to views/index.jade file:

html
head
script(data-main='js/config', src='js/require.js')
body
#corpus Hello, world!

And finally update our Gruntfile.js to execute new task:

module.exports = function(grunt) {
  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    typescript: {
      base: {
        src: ['src/**/*.ts'],
        dest: './dist/www/js',
        options: {
          module: 'amd', //or commonjs
          target: 'es5', //or es3
          base_path: 'src'
        }
      }
    },
    copy: {
      libs: {
        files: [
          { flatten: true, expand: true, src: ['lib/require.js'], dest: 'dist/www/js/'}
        ]
      },     
      tizen: {
        files: [
          { flatten: true, expand: true, src: ['platform/tizen/**'], dest: 'dist/tizen'},          
          { flatten: true, expand: true, src: ['dist/www/index.html'], dest: 'dist/tizen'},
          { flatten: true, expand: true, src: ['dist/www/js/*'], dest: 'dist/tizen/js'}          
        ]
      }
    },
    zip: {
      tizen: {
        src: 'dist/tizen/*',
        cwd: 'dist/tizen',
        dest: 'dist/helloWorld.wgt'
      }
    },
    jade: {
      compile: {
        options: {
          data: {
            debug: true
          }
        },
        files: {
          "dist/www/index.html": ["views/*.jade"]
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-typescript');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-zip');
  grunt.loadNpmTasks('grunt-contrib-jade');

  // Default task(s).
  grunt.registerTask('default', ['copy:libs', 'typescript', 'jade']);
  grunt.registerTask('tizen', ['default', 'copy:tizen', 'zip:tizen']);
};

This approach has added benefit of simpler structure (no need to use closing tags), better readability and ability to use inheritance, blocks and includes.

Stylus - CSS templates

Similar approach as for views can be used for CSS by using Stylus; in this case benefit is less obvious but with help of in-line conditioning and functions which can take away some of the maintenance problems. Approach to integrate with our solution is straightforward:

> npm install grunt-contrib-stylus --save-dev

Put initial style definition into styles/index.styl:

body
font: 62.5% "Trebuchet MS", sans-serif

#canvas
margin: 8px

Update views/index.jade:

html
head
link(rel='stylesheet', href='css/index.css')
script(data-main='js/config', src='js/require.js')
body
#corpus Hello, world!

And finally update Gruntfile.js:

module.exports = function(grunt) {
  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    typescript: {
      base: {
        src: ['src/**/*.ts'],
        dest: './dist/www/js',
        options: {
          module: 'amd', //or commonjs
          target: 'es5', //or es3
          base_path: 'src'
        }
      }
    },
    copy: {
      libs: {
        files: [
          { flatten: true, expand: true, src: ['lib/require.js'], dest: 'dist/www/js/'}
        ]
      },     
      tizen: {
        files: [
          { flatten: true, expand: true, src: ['platform/tizen/**'], dest: 'dist/tizen'},          
          { flatten: true, expand: true, src: ['dist/www/index.html'], dest: 'dist/tizen'},
          { flatten: true, expand: true, src: ['dist/www/js/*'], dest: 'dist/tizen/js'},
          { flatten: true, expand: true, src: ['dist/www/css/*'], dest: 'dist/tizen/css'}
        ]
      }
    },
    zip: {
      tizen: {
        src: 'dist/tizen/*',
        cwd: 'dist/tizen',
        dest: 'dist/helloWorld.wgt'
      }
    },
    jade: {
      compile: {
        options: {
          data: {
            debug: true
          }
        },
        files: {
          "dist/www/index.html": ["views/*.jade"]
        }
      }
    },
    stylus: {
      compile: {
        files: {
          'dist/www/css/index.css': 'styles/index.styl'
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-typescript');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-zip');
  grunt.loadNpmTasks('grunt-contrib-jade');
  grunt.loadNpmTasks('grunt-contrib-stylus');

  // Default task(s).
  grunt.registerTask('default', ['copy:libs', 'typescript', 'jade', 'stylus']);
  grunt.registerTask('tizen', ['default', 'copy:tizen', 'zip:tizen']);
};

Maintenance

Important part of software development process is making sure that everyone in the team starts from same spot and follows same rules and avoid common mistakes. First part of this process can be automated by linting (CSSLint, JSLint, JSHint etc.), unit testing (QUnit), adding clean task and second part can be done by code peer reviews. Let's start by adding some more tasks:

> npm install grunt-contrib-csslint grunt-contrib-jshint grunt-contrib-qunit grunt-contrib-clean --save-dev

Now let's make clean & lint part of our daily workflow by updating Gruntfile.js:

module.exports = function(grunt) {
  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    typescript: {
      base: {
        src: ['src/**/*.ts'],
        dest: './dist/www/js',
        options: {
          module: 'amd', //or commonjs
          target: 'es5', //or es3
          base_path: 'src'
        }
      }
    },
    copy: {
      libs: {
        files: [
          { flatten: true, expand: true, src: ['lib/require.js'], dest: 'dist/www/js/'}
        ]
      },     
      tizen: {
        files: [
          { flatten: true, expand: true, src: ['platform/tizen/**'], dest: 'dist/tizen'},          
          { flatten: true, expand: true, src: ['dist/www/index.html'], dest: 'dist/tizen'},
          { flatten: true, expand: true, src: ['dist/www/js/*'], dest: 'dist/tizen/js'},
          { flatten: true, expand: true, src: ['dist/www/css/*'], dest: 'dist/tizen/css'}
        ]
      }
    },
    zip: {
      tizen: {
        src: 'dist/tizen/*',
        cwd: 'dist/tizen',
        dest: 'dist/helloWorld.wgt'
      }
    },
    jade: {
      compile: {
        options: {
          data: {
            debug: true
          }
        },
        files: {
          "dist/www/index.html": ["views/*.jade"]
        }
      }
    },
    stylus: {
      compile: {
        files: {
          'dist/www/css/index.css': 'styles/index.styl'
        }
      }
    },
    csslint: {
      strict: {
        options: {
          import: 2
        },
        src: ['dist/www/css/*.css']
      }
    },
    jshint: { 
      files: [
        'dist/www/js/main.js',
        'dist/www/js/config.js'
      ],            
      options: { 
        force: true,  // Don't fail hard ..
        browser: true,        
        devel: true,
        globals: {define: true},
      }
    },
    clean: ['dist']
  });

  grunt.loadNpmTasks('grunt-typescript');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-zip');
  grunt.loadNpmTasks('grunt-contrib-jade');
  grunt.loadNpmTasks('grunt-contrib-stylus');
  grunt.loadNpmTasks('grunt-contrib-csslint');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-clean');

  // Default task(s).
  grunt.registerTask('default', ['copy:libs', 'typescript', 'jade', 'stylus', 'csslint', 'jshint']);  
  grunt.registerTask('tizen', ['clean', 'default', 'copy:tizen', 'zip:tizen']);
};

By running grunt command we will run into couple of issues in both CSS and JS files (bad, bad developer!). Most of those can be easily resolved except for W033 (missing semicolon) which is not generated by Typescript compiler in current version so we turn off hard fail for JS validation.
With clean task integrated into tizen target we can ensure that everybod will always receive same build from same source files; clean task can be run also directly to force cleaning of dist directory.

Complete project can be downloaded here without node_modules folder so it is necessary to run following command from project directory to load dependencies:

> npm install .

Continue to Part III or back to Part I

Žiadne komentáre:

Zverejnenie komentára