Friday 17 February 2017

Using Grunt in an AEM Maven build

This post is a guide on how to establish a functional Maven build for AEM that includes Bower front-end dependency management and Grunt task management. The Grunt build will download Bower dependencies, move them to a client library, then create their respective .content.xml, css.txt, and js.txt to be used at the AEM component level.
Prerequisites for this post:
  • AEM instance (requires Java SDK)
  • Maven (Link)
  • NodeJS w/ npm (Link)
  • Grunt (Link)
  • Bower (Link)
  • Optional reference file packet (Link)
After installing above dependencies, set up a mvn project using the below code in a terminal or command prompt: mvn archetype:generate -DarchetypeRepository=http://repo.adobe.com/nexus/content/groups/public/ -DarchetypeGroupId=com.day.jcr.vault -DarchetypeArtifactId=multimodule-content-package-archetype -DarchetypeVersion=1.0.2 -DgroupId=my-group-id -DartifactId=gruntproject -Dversion=1.0-SNAPSHOT -Dpackage=com.grunt.gruntproject -DappsFolderName=gruntproject -DartifactName="Grunt Project" -DcqVersion="5.6.1" -DpackageGroup="Grunt"
The above code will create a folder called "gruntproject", inside this folder enter the "content" folder. Inside this content folder there is a pom.xml file. In this file, find the <build> tag, then the <plugins> tag under it, then add this as the first item:
<plugin>
  <artifactId>maven-antrun-plugin</artifactId>
  <version>1.7</version>
  <executions>
      <execution>
          <phase>generate-sources</phase>
          <configuration>
              <target name="building">
                  <echo>
                      <!--  NPM INSTALL  -->
                  </echo>
                  <exec executable="cmd" dir="${project.basedir}"
                        osfamily="windows" failonerror="true">
                      <arg line="/c npm config set color false"/>
                  </exec>
                  <exec executable="bash" dir="${project.basedir}"
                        osfamily="unix" failonerror="true">
                      <arg line="npm config set color false"/>
                  </exec>
                  <exec executable="cmd" dir="${project.basedir}"
                        osfamily="windows" failonerror="true">
                      <arg line="/c npm install"/>
                  </exec>
                  <exec executable="bash" dir="${project.basedir}"
                        osfamily="unix" failonerror="true">
                      <arg line="npm install"/>
                  </exec>
                  <echo>
                      <!-- GRUNT  -->
                  </echo>
                  <exec executable="cmd" dir="${project.basedir}"
                        osfamily="windows" resultproperty="cmdresult">
                      <arg line="/c grunt --no-color >
grunt.status "/>
                  </exec>
                  <exec executable="bash" dir="${project.basedir}"
                        osfamily="unix" resultproperty="cmdresult">
                      <arg line="grunt --no-color > grunt.status"/>
                  </exec>
                  <loadfile property="grunt.status"
                            srcFile="grunt.status"/>
                  <echo>${grunt.status}</echo>
                  <delete file="grunt.status" quiet="true"/>
                  <condition property="cmdsuccess">
                      <equals arg1="${cmdresult}" arg2="0"/>
                  </condition>
                  <fail unless="cmdsuccess"/>
              </target>
          </configuration>
          <goals>
              <goal>run</goal>
          </goals>
      </execution>
  </executions>
</plugin>
</code>
Next to the pom.xml create the Grunt package.json. This file starts the individual Grunt configuration process (individual project goals may vary). The goals of this project are specific to Bower, moving files, and creating files.
{
  "name": "grunt-project",
  "version": "0.1.0",
  "devDependencies": {
    "grunt": "~0.4.5",
    "grunt-bower-task": "^0.4.0",
    "grunt-contrib-copy": "^1.0.0",
    "grunt-file-creator": "^0.1.3"
  }
}
Next to the package.json file, create the bower.json file. This file will hold the Bower dependencies that will be downloaded in the build.
{
  "name": "grunt-project",
  "description": "grunt project bower file",
  "main": "",
  "authors": [
    "Erik Johnsen <erik.johnsen@6DGlobal.com>"
  ],
  "license": "MIT",
  "homepage": "",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "dependencies": {
    "video.js": "videojs#^5.10.7"
  }
}
Finally, create the Gruntfile.js at the same level as the other created files.
var config = {
  siteName: 'gruntproject',
  bowerDirectory: 'bower_components',
  etcDirectory: 'src/main/content/jcr_root/etc/clientlibs/gruntproject',
  contentFile: function(clientLibName){
    return '<?xml version="1.0" encoding="UTF-8"?> \n'+
      '<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:rep="internal" \n' +
      '    categories="'+ config.siteName + '.' + clientLibName+'" \n'+
      '    jcr:primaryType="cq:ClientLibraryFolder" />';
  }
};

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    config: config,
    pkg: grunt.file.readJSON('package.json'),
    bower: {
      install: {
        options: {
          copy: false
        }
      }
    },
    copy: {
      dist: {
        files: [
          //videojs
          {
            expand: true,
            cwd: '<%= config.bowerDirectory %>/video.js/dist/',
            src: '**',
            dest: '<%= config.etcDirectory %>/videojs/'
          }
        ]
      }
    },
    "file-creator": {
      videojs: {
        files: [
          {
            file: '<%= config.etcDirectory %>' + "/videojs/.content.xml",
            method: function (fs, fd, done) {
              fs.writeSync(fd, config.contentFile('videojs'));
              done();
            }
          },
          {
            file: '<%= config.etcDirectory %>' + "/videojs/css.txt",
            method: function (fs, fd, done) {
              var siteName = config.siteName;
              fs.writeSync(fd, 'video-js.min.css');
              done();
            }
          },
          {
            file: '<%= config.etcDirectory %>' + "/videojs/js.txt",
            method: function (fs, fd, done) {
              var siteName = config.siteName;
              fs.writeSync(fd, 'video.min.js');
              done();
            }
          }
        ]
      }
    }
  });

  // Load the plugins
  grunt.loadNpmTasks('grunt-bower-task');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-file-creator');

  // Default task(s).
  grunt.registerTask('default', ['bower', 'copy', 'file-creator']);

};
After all the files have been created, add <filter root="/etc/clientlibs/gruntproject"/> under <filter root="/apps/gruntproject"/> to the content filter.xml
We're ready to start the fun. In terminal or command line at the top-level pom.xml file (/gruntproject) run mvn clean install -PautoInstallPackage. This will create a functional client library called gruntproject.videojs.
Below is a very simple page component to test, the video plays so it is a success:
<div data-sly-use.clientLib="${'/libs/granite/sightly/templates/clientlib.html'}" data-sly-unwrap></div>
<html>
    <head>
        <meta data-sly-call="${clientLib.css @ categories='gruntproject.videojs'}" data-sly-unwrap></meta>
    </head>
    <body>
        <video id="my-video" class="video-js" controls preload="auto" width="640" height="264"
               poster="MY_VIDEO_POSTER.jpg" data-setup="{}">
            <source src="//vjs.zencdn.net/v/oceans.mp4" type='video/mp4'>
            <p class="vjs-no-js">
                To view this video please enable JavaScript, and consider upgrading to a web browser that
                <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a>
            </p>
        </video>
        <meta data-sly-call="${clientLib.js @ categories='gruntproject.videojs'}" data-sly-unwrap></meta>
    </body>
</html>

So what actually happened here?

  • The first step is Maven used an Ant plugin to call npm install. NPM install installs all Grunt dependencies that are in the package.json file.
  • Next Maven used the Ant plugin to call grunt. This runs the Grunt build in Gruntfile.js. The Gruntfile.js did multiple actions inside its build. The default grunt call invokes this line grunt.registerTask('default', ['bower', 'copy', 'file-creator']);
    • First Grunt runs bower. This will download all dependencies in bower.json.
    • Next Grunt runs copy. Copy in this case moves the videojs dist folder to its respecitve final client libs location (<%= config.etcDirectory %>/videojs/ or src/main/content/jcr_root/etc/gruntproject/clientlibs/videojs)
    • Lastly, using the file-creator Grunt generates the simple .content.xml, css.txt, and js.txt using some of the custom variables and functions in the Gruntfile.js config.
  • The result is a VideoJS client library with the category gruntproject.videojs

Where to go from here

This was a relatively simple example used to illustrate how Grunt can be used in AEM to create a single client library. This method used Grunt, but if more familiar with Gulp, it would be easy to change to Gulp. Gulp dependencies would need to be installed npm install --global gulp-cli. Then in the pom.xml, just change the grunt calls to gulp. On the subject of this, those grunt calls can also be extended to include arguments if different builds are required. Finally, if looking for a less manual way to build client libraries Grunt plugin grunt-aem-clientlib-generator can be used.

No comments :

Post a Comment