Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
sathishkv
Explorer

It was in early 2013 that I got exposed to and started using grunt heavily for performing the build activities on one of the HTML based UI projects and recently I had the opportunity to also try it out with an UI5 application. Grunt provides a wide range of tasks and this makes activities like static checks, minifaction, etc a simple operation. When I started with the UI5 build process, the initial few hours were spent on defining an appropriate build process (depicted and explained below).

Dependencies

Before we look into the build details, let me list down the grunt plugins that were used for the build purpose.


"devDependencies": {
"grunt": "~0.4.5",
"grunt-contrib-clean" : "~0.5.0",
"grunt-contrib-concat" : "~0.4.0",   
"grunt-contrib-copy" : "~0.5.0",
"grunt-contrib-uglify" : "~0.5.0",   
"grunt-contrib-less" : "~0.11.0",         
"grunt-contrib-jshint" : "~0.10.0",   
"grunt-contrib-compress" : "~0.9.0",
"grunt-contrib-csslint" : "~0.2.0"   
  }




The following are the reference paths that are used in the build. These were maintained in a separate GruntRef.json file that resides in the application root.



{
      "source"    : "src/",              //contains the source files
      "staging"   : "build/staging/",     //used for staging phase
      "process"   : "build/process/",     //used for process phase
      "finalize"  : "build/finalize/",    //used for finalization phase
      "output"    : "build/output         //contains the final output
}




The “source” folder could also be your “WebContent” folder if a separate project is used for development.

Prepare Phase

The preparation phase starts with a cleanup of the existing folders, if any. The build process was defined in such a way that the entire build happens in a separate local folder which is overwritten with each build. As a result, the previous build output has to be wiped clean before starting with the new build.


clean: {
prepare: {
              //Release the folder build/process
              src: ["<%= ref.process%>", "<%= ref.staging%>", "<%= ref.output%>", "<%= ref.finalize%>"]
                 },


Once the cleanup is done, the source files are copied from the source into the staging area. Only those files that are essential for final deployment are copied and the rest are skipped (for e.g. the unit tests, local index, etc)


copy: {
              prepare: {
                     //copy all the files from WebContent before we start the processing
                     //The files are copied into the staging area
                     files: [
                     /*
                            Copy all files to prepare for further processing
                     */                        
                      {
                            expand : true,
                            cwd    : "<%= ref.source%>",
                            src : ['**/*', '!data/**', '!util/MockServer.js', '!META-INF/**', '!WEB-INF/**', '!test/**', '!test_local.html', '!index_local.html'],
                             dest : "<%= ref.staging%>"
                     }
                     ]
              },


Process Phase

During the process phase the following actions are performed by the build.

Static Checks

The static checks are performed on the original code in the staging area and the report is downloaded as a CSV file. The JSHint module was used for performing the static checks


       jshint: {
              process: {
                     options : {
                            eqeqeq : true,
                            es3    : true,
                            unused : true,
                            eqnull : true,
                            camelcase : true,
                            laxbreak  : true,
                            force  : true,
                            reporter : "jshint-reporter",
                            reporterOutput : "<%= ref.output%>JSLint.csv"
                     },
                     src: "<%= ref.staging%>**/*.js"
              }
       },


The below reporter function enables in generating a custom CSV file for the error generated by the static check. The reporter was placed in a separate file in the application root (along with the gruntfile.js)


reporter: function (res) {
    var len = res.length;
    var str = "";
    // Header
    str += "Filepath , Type, Code, Line , Column , Reason , Evidence \n"
res.forEach(function (r) {
      var file = r.file;
      var err = r.error;
      str += file + ", "
              + err.id + ", "
              + err.code + ", "
              + err.line + ", "
              + err.character + ", "
+ err.reason + ", "
+ err.evidence + "\n";
    });
    if (str) {
process.stdout.write(str);
    }
  }


The build process does not terminate if there are errors in the static check. This can however be changed by turning the jshint option “force” to “false”. The decision on when to stop the build due to errors in static checks can vary from project to project. The CSV file is directly written to the “output” folder as this would be one of the final outputs of the build process.

Transformation

Once the static checks are done on the original source in the staging area, it is time to perform any code generation / transformation. In this build, I have taken the case of Less to CSS transformation. The “less” grunt plugin performs this transformation


less: {
  process: {
  options: {
  compress : true,
  cleancss : true,
  sourceMap : true
  },
  files: {
  "<%= ref.process%>style/root.css" : "<%= ref.staging%>style/root.less"
  }
  }
  },

Minification

Minification is performed using the highly popular plugin “uglify”. The source for the minified are picked from the staging area and placed in the process folders.  Other file types can also be minified in this step using the relevant plugins. At the end of this process step all files are minified and available in the process folder.



       uglify: {
              options : {
                     mangle : true,
                     compress : {
                            drop_console : true,
                            dead_code : false,
                            unused        : false
                     }                         
              },
              files : {
                     expand : true,
                     cwd    : "<%= ref.staging%>",
                     src : ["**/*.js", '!test/**', '!test_local.html'],
                     dest   : "<%= ref.process%>"
              }
       },


Debug File Generation

Though the minified files that have been placed in the process folder should be good enough for running the application, we need to also generate the debug files that would help in debugging the application in UI5 debug mode. To achieve this I have used the “copy” plugin that also copies all the other files (Note: I have minified only the JS files and not the other file types). During the copy process, the JS files are renamed to include the “-dbg” suffix. However this is not completely straightforward. For the controllers, the “-dbg” should be suffixed to the view name (i.e., before the “.controller” substring)


process: {
  files: [
  /*
  Copy JS files to create the dbg version
  */    
         {
          expand : true,
          cwd : "<%= ref.staging%>",
          src : ['**/*.js', '**/*.html', '**/*.xml', '**/*.jpg', '**/*.png', '**/*.gif', '**/*version.json', '**/*.properties'],
          dest : "<%= ref.process%>",
          rename : function(dest, src){
               var aSrc = src.split('.');
               var ext = aSrc.pop();
               if(ext === 'js') {
                    if(aSrc.length > 1){
                         if(aSrc[aSrc.length - 1] === "controller"){
                              ext = aSrc.pop() +"." + ext;
                         }
                    }
                    return dest + aSrc.join('.') + '-dbg.' + ext;
               }     else {
                    return dest + src;
               }
          }
         }
        ]
  }


Note: Before the debug files are created, the Component-preload.js is created by concatenating all utils, controls and other relevant files. I have deliberately kept the view controllers out of the preload as in my case I did not want to load all the views in one request but load them only on demand.

At the end of these steps, the files have been processed and are ready to be deployed.

Finalization Phase

The finalization phase involves the following steps.

File Headers

It may be required to add file headers to the final output. This can be added using the concat plugin. The files are then copied to the finalize folder.


Generate Archive

The compress plugin helps in creating an archive from the source code. In this case I am creating a simple ZIP file of the processed files which would then be deployed in the target WebContent folder.


compress: {
       finalize: {
              options: {
                     archive: "<%= ref.output%>build<%= grunt.template.today('yyyymmddhhmmss')%>.zip"
              },
              files: [{
                     expand : true,
                     cwd    : "<%= ref.finalize%>",
                     src    : "**/*",
                     dest : ""
              }]
       }
}


Clean-up

The final step is to clean-up all the folders, except the output folder.


copy :  {
...
finalize: {
              //Release the folder build/_process
              src: ["<%= ref.staging%>", "<%= ref.process%>", "<%= ref.finalize%>"]
}
}


The above process is just one of the many ways in which grunt can be used for building the SAPUI5 application. The build can be further simplified depending on the specific requirement of the project. To end, the tasks that were used in the final build is given below.


     grunt.registerTask('default', ['clean:prepare', 'copy:prepare',
          'jshint:process', 'less:process', 'uglify', 'concat:process',
          'clean:process', 'copy:process', 'concat:finalizeJS',
          'concat:finalizeML', 'concat:finalizeProp', 'copy:finalize',
          'updateVersion',  'compress:finalize', 'clean:finalize']);


3 Comments