Using Front-end Technologies like Node, Grunt, and Sass with AEM

Using Front-end Technologies like Node, Grunt, and Sass with AEM

In this article, I am going to show you how to use Grunt and Grunt plugins to compile Sass into CSS, minify CSS, add CSS source maps, merge JavaScript files, minify the JS, and add JavaScript source maps. We will be using Bower to add vendor JavaScript libraries to our project.  Before I show you the “how” I want to go over the “why.” This is important because AEM provides many of this functionality out of the box. Let me give you a bit of my history.

My background differs from most of my Adobe Experience Manager (AEM) colleagues. My background is in front-end technologies. Many of my coworkers have been immersed in backend technologies since their earliest days as developers. They can speak at length on the most efficient sorting algorithm and know why an ArrayList is a better choice than a Vector if your program is thread-safe. However, watch them debug a page layout when the z-indexes are out of whack and you will see their confidence fade away.  As a child, whenever someone asked me what I wanted to be when I grew up I’d say “I’m going to be an artist.” This was before the Internet. After the internet, all I wanted to do was draft and design clean and attractive websites.  As the sites I developed became more and more advanced, back-end technologies, such as Java Servlets, JSP, and ORMs were utilized to add functionality.

Adobe is sold to marketers as a marketing platform. As a general rule, marketers are visual people. They know the power of visual story telling. I can appreciate that.  This trait is not a requirement to become an AEM developer. Usually a design team is employed to help the client tell their story. It is the job of the AEM developer to implement the vision of the design team and the client.

Here’s where I see things breaking down. Since the advent of Node.js, tools empowered by Node.js have been popping up every day. The design team will most likely make use of these front-end technologies.  The might use Yeoman to scaffold the site. The probably will use preprocessors such as Less, Sass, Stylus, or PostCSS to simplify the creation of stylesheets.  If the frontend developers are really slick, they might write their code in ECMAScript 6 compliant JavaScript and use Babel to transpile their JavaScript into something the browsers can understand.  Heck, they might write in an entirely new dialect of JavaScript like CofffeScript.  They might use JavaScript testing libraries like Mocha or Jasmine to unit test their code.  They will use task runners like Gulp or Grunt to compile, transpile, and minify the code before they hand it off to the AEM developer.  This code is often so optimized it becomes difficult to read, difficult to edit, and difficult to debug.  An unintended consequence of this process is the AEM developer becomes dependent on the design team for the smallest change.  What if we did the compiling and transpiling in our build process? What if we received the raw Sass, Less, or CoffeeScript file right from the front-end developers? We can afford to learn a few more programming languages.  Node.js is not going away.  Any exposure to it today, can only help us tomorrow.

OK – On to the code …

We are going to build a quick site based on the Parallax Template found here: http://materializecss.com/getting-started.html.

Although not necessary to get this project to build, I advise you to install Node.js (https://nodejs.org/en/).  Some of the steps I go through below require Node.js to be installed.  Our Maven project will download a local copy of Node.js.  This will allow our project to run on machines where Node.js is not installed.  If you use the same version of Node.js in your maven project as you have installed on your machine, you will be able to run the commands Maven calls for yourself.  This is helpful if you run into problems and wish to take Maven out of the equation.  We will also be using bower (https://bower.io/).  Bower will be installed by Node.js. Bower requires Git (https://git-scm.com/). Git will not be installed by Node.js and must exist on every machine where this project is to be run.

I usually use the “AEM project archetype” as the starting point for all my AEM projects.   then remove the code I don’t need.  You can download the base project here.

From within the Parallax base project, create a folder named “workflow” within the following directory ui.apps/src/main.  This is where we will be doing all our Node.js magic.

Note: I’ve called this directory workflow (not to be confused with AEM workflows) because it is within this directory we will be running our Grunt tasks or workflows

Create a file within our “workflow” directory named: package.json.  You can just as easily create the file by running “npm init” from within this directory.  I find “npm init” adds more information than is needed to the file.  Set the file’s contents to look like the following:

{
"name": "Parallax",
"version": "1.0.0",
"description": "Workflow to install JavaScript Libraries, compile Sass to CSS, and combine and minify JS.",
"author": "Shannon Sumner"
}

Let’s add the Node.js packages we will be using in our build to the package.js file.  From a command line within the workflow directory type:

npm install grunt --save
npm install grunt-bower-task --save
npm install grunt-contrib-uglify --save
npm install grunt-sass –save
npm install grunt-banner –save
npm –g install grunt-cli –save

Your package.json file should now resemble the following:

{
"name": "parallax",
"version": "1.0.0",
"description": "Workflow to install JavaScript Libraries, compile Sass to CSS, and combine and minify JS.",
"author": "Shannon Sumner",
"dependencies": {
  "grunt": "^1.0.1",
  "grunt-banner": "^0.6.0",
  "grunt-bower-task": "^0.4.0",
  "grunt-cli": "^1.2.0",
  "grunt-contrib-uglify": "^1.0.1",
  "grunt-sass": "^1.2.0"
  }
}

Also install the following NPM packages globally (you might need su rights to do this …)

npm -g install bower
npm –g install grunt-cli

Now run “bower init” and choose the following options:

? name parallax
? description
? main file
? keywords
? authors Shannon Sumner <shannonsumner@gmail.com>
? license MIT
? homepage
? set currently installed components as dependencies? No
? add commonly ignored files to ignore list? Yes
? would you like to mark this package as private which prevents it from being accidentally published to the registry? Yes

We will be using the “Materialize” Stylesheet and JavaScript files in our project and instead of hunting the web for them, we can simple run:

bower search materialize

Bower will show you all packages matching the term “materialize”.  We want the first one so we will now install it and add it to our bower.json file.

bower install materialize --save

Look at the newly created bower_components directory.  You will notice bower not only downloaded Materialize but jQuery as well.  Bower is like Maven for JavaScript.  It’ll fetch the libraries you requested and all required dependencies.

The jQuery version bower installs is the latest and greatest.  Let’s dial back the version a bit and force it to use version 2.2.4 of jQuery.

bower install jquery#2.2.4 --save

Your bower.json file should resemble the following:

{
"name": "parallax",
"description": "",
"main": "",
"authors": [
  "Shannon Sumner <shannonsumner@gmail.com>"
  ],
"license": "MIT",
"homepage": "",
"private": true,
"ignore": [
  "**/.*",
  "node_modules",
  "bower_components",
  "test",
  "tests"
  ],

"dependencies": {
  "Materialize": "materialize#^0.97.6",
  "jquery": "2.2.4"
  }
}

The creation of our GruntFile.js will not be so automated.  We will need to define each of our JavaScript tasks by hand.  Let’s start with an empty GruntFile.js. Create a file named “GruntFile.js” in your workflow directory with the following contents:

/*global module:false*/
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
});
// These plugins provide necessary tasks.
// Default task.
grunt.registerTask('default', []);
};

Now let’s add some config variables.   The first variable we will add is “pkg”.  “pkg” will contain the contents of our “package.json” file.

// Project configuration.
grunt.initConfig({
  pkg: grunt.file.readJSON('package.json')
});

I like to add a banner to my generated Stylesheets & JavaScript files. Inclusion of package name, version, and creation date helps me to determine if I am using the correct version of the CSS or JS.  It is also very cool. Add the following config variable:

banner: '/*!\n' +
'* 8888888b.     d8888 8888888b.         d8888 888      888             d8888 Y88b   d88P *\n' +
'* 888   Y88b   d88888 888   Y88b       d88888 888      888            d88888  Y88b d88P  *\n' +
'* 888    888  d88P888 888    888      d88P888 888      888           d88P888   Y88o88P   *\n' +
'* 888   d88P d88P 888 888   d88P     d88P 888 888      888          d88P 888    Y888P    *\n' +
'* 8888888P" d88P  888 8888888P"     d88P  888 888      888         d88P  888    d888b    *\n' +
'* 888      d88P   888 888 T88b     d88P   888 888      888        d88P   888   d88888b   *\n' +
'* 888     d8888888888 888  T88b   d8888888888 888      888       d8888888888  d88P Y88b  *\n' +
'* 888    d88P     888 888   T88b d88P     888 88888888 88888888 d88P     888 d88P   Y88b *\n' +
'*                                                                                        *\n' +
'* Version: <%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd, hh:MM TT") %>                                                  *\n' +
'* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>                                                      *\n' +
'*/\n'

Now that we have our config variables in place, let’s add our first task.  Before we can do anything we will need our Materialize JavaScript library and Sass files in place.  Let’s add the task to run our “bower.json” file we created earlier. After the banner config variable add the following task configuration:

// Task configuration.
bower: {
  install: {
    options: {
    targetDir: '../content/jcr_root/etc/designs/parallax/clientlib/lib',
    layout: 'byComponent',
    install: true,
    verbose: true,
    cleanTargetDir: true,
    cleanBowerDir: false
    }
  }
}

After this comment “These plugins provide necessary tasks”, load the NPM task to run the bower task:

// These plugins provide necessary tasks.
grunt.loadNpmTasks('grunt-bower-task');

Now let’s register the gunt task “bower”:

grunt.registerTask('default', ['bower']);

OK Let’s try out our Grunt File.  From the command line run “grunt default”.

The output of your command should look similar to the following:

(node:13227) fs: re-evaluating native module sources is not supported. If you are using the graceful-fs module, please update it to a more recent version.
(node:13227) fs: re-evaluating native module sources is not supported. If you are using the graceful-fs module, please update it to a more recent version.
>> Installed bower packages
grunt-bower copying bower_components/jquery/dist/jquery.js -> ../content/jcr_root/etc/designs/parallax/clientlib/lib/jquery/jquery.js
grunt-bower copying bower_components/Materialize/bin/materialize.css -> ../content/jcr_root/etc/designs/parallax/clientlib/lib/Materialize/materialize.css
grunt-bower copying bower_components/Materialize/bin/materialize.js -> ../content/jcr_root/etc/designs/parallax/clientlib/lib/Materialize/materialize.js
grunt-bower copying bower_components/Materialize/fonts/roboto/Roboto-Bold.ttf -> ../content/jcr_root/etc/designs/parallax/clientlib/lib/Materialize/Roboto-Bold.ttf
grunt-bower copying bower_components/Materialize/fonts/roboto/Roboto-Bold.woff -> ../content/jcr_root/etc/designs/parallax/clientlib/lib/Materialize/Roboto-Bold.woff
grunt-bower copying bower_components/Materialize/fonts/roboto/Roboto-Bold.woff2 -> ../content/jcr_root/etc/designs/parallax/clientlib/lib/Materialize/Roboto-Bold.woff2
grunt-bower copying bower_components/Materialize/fonts/roboto/Roboto-Light.ttf -> ../content/jcr_root/etc/designs/parallax/clientlib/lib/Materialize/Roboto-Light.ttf
grunt-bower copying bower_components/Materialize/fonts/roboto/Roboto-Light.woff -> ../content/jcr_root/etc/designs/parallax/clientlib/lib/Materialize/Roboto-Light.woff
grunt-bower copying bower_components/Materialize/fonts/roboto/Roboto-Light.woff2 -> ../content/jcr_root/etc/designs/parallax/clientlib/lib/Materialize/Roboto-Light.woff2
grunt-bower copying bower_components/Materialize/fonts/roboto/Roboto-Medium.ttf -> ../content/jcr_root/etc/designs/parallax/clientlib/lib/Materialize/Roboto-Medium.ttf
grunt-bower copying bower_components/Materialize/fonts/roboto/Roboto-Medium.woff -> ../content/jcr_root/etc/designs/parallax/clientlib/lib/Materialize/Roboto-Medium.woff
grunt-bower copying bower_components/Materialize/fonts/roboto/Roboto-Medium.woff2 -> ../content/jcr_root/etc/designs/parallax/clientlib/lib/Materialize/Roboto-Medium.woff2
grunt-bower copying bower_components/Materialize/fonts/roboto/Roboto-Regular.ttf -> ../content/jcr_root/etc/designs/parallax/clientlib/lib/Materialize/Roboto-Regular.ttf
grunt-bower copying bower_components/Materialize/fonts/roboto/Roboto-Regular.woff -> ../content/jcr_root/etc/designs/parallax/clientlib/lib/Materialize/Roboto-Regular.woff
grunt-bower copying bower_components/Materialize/fonts/roboto/Roboto-Regular.woff2 -> ../content/jcr_root/etc/designs/parallax/clientlib/lib/Materialize/Roboto-Regular.woff2
grunt-bower copying bower_components/Materialize/fonts/roboto/Roboto-Thin.ttf -> ../content/jcr_root/etc/designs/parallax/clientlib/lib/Materialize/Roboto-Thin.ttf
grunt-bower copying bower_components/Materialize/fonts/roboto/Roboto-Thin.woff -> ../content/jcr_root/etc/designs/parallax/clientlib/lib/Materialize/Roboto-Thin.woff
grunt-bower copying bower_components/Materialize/fonts/roboto/Roboto-Thin.woff2 -> ../content/jcr_root/etc/designs/parallax/clientlib/lib/Materialize/Roboto-Thin.woff2
>> Copied packages to /Users/shannonsumner/Documents/workspace/parallax/ui.apps/src/main/content/jcr_root/etc/designs/parallax/clientlib/lib
Done.

So what did it do?  It downloaded Materialize from the bower repository and moved the files necessary to get Materialize working to a folder named “lib” in our clientlib directory. Pretty cool, but not what we need.  We need the sass files, not the compiled css file, we would also like the files organized a bit better.  Let’s open our bower.json file and the following option:

"exportsOverride": {
  "Materialize": {
    "js": "dist/js/materialize.js",
    "sass": "sass/*.scss",
    "sass/components": "sass/components/*.scss",
    "sass/components/date_picker": "sass/components/date_picker/*.scss",
    "sass/components/forms": "sass/components/forms/*.scss",
    "fonts": "fonts/roboto/**"
    },
  "jquery": {
    "": "dist/jquery.js"
    }
}

These options will move our Materialize JavaScript, Sass, and Fonts to js, sass, and fonts folders respectively under the Materialize directory located within lib.  It will also move jquery.js to a jquery folder located within lib. We will not be referencing the minified versions of these JavaScript libraries.  Another Grunt task will do the concatenation and minification. This task will also produce source maps pointing back to the original JavaScript libraries.  Look here if your interested on how source maps work: http://www.mattzeunert.com/2016/02/14/how-do-source-maps-work.html.

Now that we have the Materialize Sass files under lib, let’s reference them within our Client Library. Create a folder under /etc/designs/parallax/clientlib named “sass”.  Create a file named “main.scss” under the sass directory you just created.  Add this line to main.scss:

@import '../lib/Materialize/sass/materialize';

That’s it.  We’ve just reference the Materialize Sass files within our project. Sass imports work differently than CSS imports.  You can learn more about them here: http://sass-lang.com/guide#topic-5.

Time to add the Sass task to our GruntFile.js.  Add the following task configuration:

sass: {
  dist: {
    options: {
    outputStyle: 'compressed',
    sourceMap: true
    },
  files: [{
    expand: true,
    cwd: '../content/jcr_root/etc/designs/parallax/clientlib/sass',
    src: ['main.scss'],
    dest: '../content/jcr_root/etc/designs/parallax/',
    ext: '.css'
    }]
  }
}

Add the following NPM task:

grunt.loadNpmTasks('grunt-sass');

Add the sass Grunt task:

grunt.registerTask('default', ['bower', 'sass']);

Let’s run “grunt default” on the command line again. You will see this addition to the grunt output:

Running "sass:dist" (sass) task

Not quite as impressive as the bower task output (We can turn off bower’s verbosity by changing the verbose option to false in the Grunt File configuration).  We should have a brand new minified CSS file as well as a CSS source map under “/etc/designs/parallax.” You might wonder why I don’t put the CSS file in the clientlib directory instead at the root of the Parallax Designer folder. Normally I would, but AEM serves the Client Library Stylesheets at this path and in order to get the relative pathsworking within the source maps, we need to put our files at this level as well.

Onto the last two Grunt tasks. The first one will add a banner to the Sass file before we process it. If we added the banner to the CSS file after the Sass was compiled, the source maps would no longer point to the correct line numbers.  The second task will combine, minify, and create sourcemaps for the JavaScript files.

Add the following task configurations:

usebanner: {
  taskName: {
    options: {
      position: 'top',
      banner: '<%= banner %>',
      linebreak: true,
      replace: true
    },
      files: {
        src: ['../content/jcr_root/etc/designs/parallax/clientlib/sass/main.scss']
      }
    }
  },
uglify: {
  options: {
  banner: '<%= banner %>',
  mangle: false,
  sourceMap: true
  },
  target: {
    files: {
      '../content/jcr_root/etc/designs/parallax/main.js': [
      '../content/jcr_root/etc/designs/parallax/clientlib/lib/jquery/jquery.js',
      '../content/jcr_root/etc/designs/parallax/clientlib/lib/Materialize/js/materialize.js',
      '../content/jcr_root/etc/designs/parallax/clientlib/js/vendor/**/*.js',
      '../content/jcr_root/etc/designs/parallax/clientlib/js/**/*.js'
      ]
    }
  }
}

Add the following NPM tasks:

grunt.loadNpmTasks('grunt-banner');
grunt.loadNpmTasks('grunt-contrib-uglify');

Add the following to the Grunt task:

grunt.registerTask('default', ['bower', 'usebanner', 'sass', 'uglify']);

OK one more time.  Let’s run “grunt default”.   You should see the following new output:

Running "usebanner:taskName" (usebanner) task
√ grunt-banner completed successfully
Running "uglify:target" (uglify) task
File ../content/jcr_root/etc/designs/parallax/main.js.map created (source map).
File ../content/jcr_root/etc/designs/parallax/main.js created: 545.28 kB → 295.84 kB
>> 1 sourcemap created.
>> 1 file created.

Looks good!  You should have a brand new minified JavaScript file as well as a JavaScript source map under “/etc/designs/parallax”.  The Grunt “uglify” (horrible name) task takes the JavaScript within lib/jquery, lib/Materialize/js, a folder named “vendor” within our clientlibs (if one exists), and the js directory under clientlibs and combines and minifies the results.  It also creates a nifty source map for debugging purposes.  If your curious how the banner looks, open up the minified CSS file or the minified JavaScript file and take a peak.  The complete GruntFile.js can be downloaded here.

Onto AEM …

We are finally ready to integrate the work we have done with Node.js, Bower, Grunt, and Sass with AEM. First thing we need to do is delete the “node_modules” and “bower_components” folder. These folders will be created by Maven at build time. If we are using the same version of Node and NPM in our Maven file as we have installed on our machine, we shouldn’t have an issue, but just to be safe, we will delete these folders… Open up the pom.xml file located under ui.apps.  Insert the following XML after the “content-package-maven-plugin” plugin. Make sure you swap out the nodeVersion and npmVersion values with the output of the following commands on your system:

node --version
npm --version
<plugin>
  <groupId>com.github.eirslett</groupId>
  <artifactId>frontend-maven-plugin</artifactId>
  <version>0.0.27</version>
  <executions>
    <execution>
      <id>install node and npm</id>
      <phase>generate-resources</phase>
      <goals>
        <goal>install-node-and-npm</goal>
      </goals>
      <configuration>
        <nodeVersion>v4.2.4</nodeVersion>
        <npmVersion>2.14.12</npmVersion>
        <workingDirectory>src/main/workflow</workingDirectory>
      </configuration>
    </execution>
    <execution>
      <id>npm install</id>
      <phase>generate-resources</phase>
      <goals>
        <goal>npm</goal>
      </goals>
      <configuration>
        <arguments>install</arguments>
        <workingDirectory>src/main/workflow</workingDirectory>
      </configuration>
    </execution>
    <execution>
      <id>grunt build</id>
      <phase>generate-resources</phase>
      <goals>
        <goal>grunt</goal>
      </goals>
      <configuration>
        <arguments>--verbose</arguments>
        <workingDirectory>src/main/workflow</workingDirectory>
      </configuration>
    </execution>
  </executions>
</plugin>

Once the Plugin XML had been added to your pom file, run “mvn clean install” from the root of ui.apps under parallax.  The “frontend-maven-plugin” will download Node.js and NPM into the workflow directory (if they are not already there) and run Node.js and NPM from this directory.  Node.js will install the dependencies indicated in the package.json file and run the Grunt task.  Grunt will run the Bower, Banner, Sass, and Uglify tasks.  The beauty of this solution is Node.js and NPM do not have to be installed on the machine where this build is running.  If you use any change management on your AEM projects, you will want to exclude the “node_modules” and “bower_components” directories (they can get quite big).  You will also want to remove these directories before sharing your project.  They will be created at run time.  If your build fails for any reason, ensure the build works when your run “npm install” and “grunt default” by hand.  More information on this great plugin can be found here: https://github.com/eirslett/frontend-maven-plugin.

Let’s build out our Parallax Page Template.

Open up “apps/parallax/components/structure/page-default/page-default.html” in your favorite editor. Replace the contents of the file with the following html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no" />
    <title>Parallax Template - Materialize</title>
    <!-- CSS  -->
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
    <link rel='stylesheet' data-sly-use.clientLib="${'/libs/granite/sightly/templates/clientlib.html'}" data-sly-call="${clientLib.css @ categories='parallax'}" data-sly-unwrap />
</head>

<body>
    <nav class="white" role="navigation">
        <div class="nav-wrapper container">
            <a id="logo-container" href="#" class="brand-logo">Logo</a>
            <ul class="right hide-on-med-and-down">
                <li><a href="#">Navbar Link</a></li>
            </ul>
            <ul id="nav-mobile" class="side-nav">
                <li><a href="#">Navbar Link</a></li>
            </ul>
            <a href="#" data-activates="nav-mobile" class="button-collapse"><i
class="material-icons">menu</i></a>
        </div>
    </nav>
    <div id="index-banner" class="parallax-container">
        <div class="section no-pad-bot">
            <div class="container">
                <br>
                <br>
                <h1 class="header center teal-text text-lighten-2">Parallax
Template</h1>
                <div class="row center">
                    <h5 class="header col s12 light">A modern responsive front-end
framework based on Material Design</h5>
                </div>
                <div class="row center">
                    <a href="http://materializecss.com/getting-started.html" id="download-button" class="btn-large waves-effect waves-light teal lighten-1">Get
Started</a>
                </div>
                <br>
                <br>
            </div>
        </div>
        <div class="parallax">
            <img src="/etc/designs/parallax/clientlib/img/background1.jpg" alt="Unsplashed background img 1">
        </div>
    </div>
    <div class="container">
        <div class="section">
            <!--   Icon Section   -->
            <div class="row">
                <div class="col s12 m4">
                    <div class="icon-block">
                        <h2 class="center brown-text">
<i class="material-icons">flash_on</i>
</h2>
                        <h5 class="center">Speeds up development</h5>
                        <p class="light">We did most of the heavy lifting for you to provide a default stylings that incorporate our custom components. Additionally, we refined animations and transitions to provide a smoother experience for developers.</p>
                    </div>
                </div>
                <div class="col s12 m4">
                    <div class="icon-block">
                        <h2 class="center brown-text">
<i class="material-icons">group</i>
</h2>
                        <h5 class="center">User Experience Focused</h5>
                        <p class="light">By utilizing elements and principles of Material Design, we were able to create a framework that incorporates components and animations that provide more feedback to users. Additionally, a single underlying responsive system across
                            all platforms allow for a more unified user experience.</p>
                    </div>
                </div>
                <div class="col s12 m4">
                    <div class="icon-block">
                        <h2 class="center brown-text">
<i class="material-icons">settings</i>
</h2>
                        <h5 class="center">Easy to work with</h5>
                        <p class="light">We have provided detailed documentation as well as specific code examples to help new users get started. We are also always open to feedback and can answer any questions a user may have about Materialize.</p>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="parallax-container valign-wrapper">
        <div class="section no-pad-bot">
            <div class="container">
                <div class="row center">
                    <h5 class="header col s12 light">A modern responsive front-end
framework based on Material Design</h5>
                </div>
            </div>
        </div>
        <div class="parallax">
            <img src="/etc/designs/parallax/clientlib/img/background2.jpg" alt="Unsplashed background img 2">
        </div>
    </div>
    <div class="container">
        <div class="section">
            <div class="row">
                <div class="col s12 center">
                    <h3>
<i class="mdi-content-send brown-text"></i>
</h3>
                    <h4>Contact Us</h4>
                    <p class="left-align light">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam scelerisque id nunc nec volutpat. Etiam pellentesque tristique arcu, non consequat magna fermentum ac. Cras ut ultricies eros. Maecenas eros justo, ullamcorper a sapien
                        id, viverra ultrices eros. Morbi sem neque, posuere et pretium eget, bibendum sollicitudin lacus. Aliquam eleifend sollicitudin diam, eu mattis nisl maximus sed. Nulla imperdiet semper molestie. Morbi massa odio, condimentum sed
                        ipsum ac, gravida ultrices erat. Nullam eget dignissim mauris, non tristique erat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;</p>
                </div>
            </div>
        </div>
    </div>
    <div class="parallax-container valign-wrapper">
        <div class="section no-pad-bot">
            <div class="container">
                <div class="row center">
                    <h5 class="header col s12 light">A modern responsive front-end
framework based on Material Design</h5>
                </div>
            </div>
        </div>
        <div class="parallax">
            <img src="/etc/designs/parallax/clientlib/img/background3.jpg" alt="Unsplashed background img 3">
        </div>
    </div>
    <footer class="page-footer teal">
        <div class="container">
            <div class="row">
                <div class="col l6 s12">
                    <h5 class="white-text">Company Bio</h5>
                    <p class="grey-text text-lighten-4">We are a team of college students working on this project like it's our full time job. Any amount would help support and continue development on this project and is greatly appreciated.</p>
                </div>
                <div class="col l3 s12">
                    <h5 class="white-text">Settings</h5>
                    <ul>
                        <li><a class="white-text" href="#!">Link 1</a></li>
                        <li><a class="white-text" href="#!">Link 2</a></li>
                        <li><a class="white-text" href="#!">Link 3</a></li>
                        <li><a class="white-text" href="#!">Link 4</a></li>
                    </ul>
                </div>
                <div class="col l3 s12">
                    <h5 class="white-text">Connect</h5>
                    <ul>
                        <li><a class="white-text" href="#!">Link 1</a></li>
                        <li><a class="white-text" href="#!">Link 2</a></li>
                        <li><a class="white-text" href="#!">Link 3</a></li>
                        <li><a class="white-text" href="#!">Link 4</a></li>
                    </ul>
                </div>
            </div>
        </div>
        <div class="footer-copyright">
            <div class="container">
                Made by <a class="brown-text text-lighten-3" href="http://materializecss.com">Materialize</a>
            </div>
        </div>
    </footer>
    <!--  Scripts-->
    <script data-sly-use.clientLib="${'/libs/granite/sightly/templates/clientlib.html'}" data-sly-call="${clientLib.js @ categories='parallax'}" data-sly-unwrap></script>
</body>

</html>

Normally I would break a template like this up into partials – but since this is just a demonstration on using Node.js packages within AEM, we will skip this step.  Open up “ui.apps/src/main/content/jcr_root/etc/designs/parallax/clientlib/.content.xml”.  Remove the dependency on cq.jquery.

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="cq:ClientLibraryFolder"
categories="[parallax]" />

Create a new file named “parallax.scss” under “ui.apps/src/main/content/jcr_root/etc/designs/parallax/clientlib/sass”.  Set the contents of that file to the following:

$tundora: #444;
$jungle-green: #26a69a;
$transparent-white: rgba(255, 255, 255, .9);
nav {
    .brand-logo,
    ul {
        a {
            color: $tundora;
        }
    }
}
p {
    line-height: 2rem;
}
.button-collapse {
    color: $jungle-green;
}
.parallax-container {
    color: $transparent-white;
    height: auto;
    line-height: 0;
    min-height: 380px;
    .section {
        width: 100%;
    }
}
@media only screen and (max-width: 992px) {
    .parallax-container {
        .section {
            position: absolute;
            top: 40%;
        }
    }
    #index-banner {
        .section {
            top: 10%;
        }
    }
}
@media only screen and (max-width: 600px) {
    #index-banner {
        .section {
            top: 0;
        }
    }
}
.icon-block {
    padding: 0 15px;
    .material-icons {
        font-size: inherit;
    }
}
footer {
    &.page-footer {
        margin: 0;
    }
}

Open “main.scss” in the same directory and add the reference to parallax.scss:

@import '../lib/Materialize/sass/materialize';
@import 'parallax';

Open “main.js” located under “ui.apps/src/main/content/jcr_root/etc/designs/parallax/clientlib/js”.  Set the contents of the file to the following:

(function($) {
  $(function() {
    $('.button-collapse').sideNav();
    $('.parallax').parallax();
  });
})(jQuery);

Extract the archive located here, and copy the images to “ui.apps/src/main/content/jcr_root/etc/designs/parallax/clientlib/img”.

Update the “css.txt” file located within the root of your clientlib directory to resemble the following:

#base=..
main.css

Change the contents of “js.txt” to resemble the following:

#base=..
main.js

Note: You must rename a Sass file in the bower_components directory or the build will fail with the following error: “OakName0001: Invalid namespace prefix.” For some reason AEM does not like underscores in the middle of a file name.

  1. Navigate to “ui.apps/src/main/workflow/bower_components”. Bower will note replace the packages stored in this directory once it has fetched them.
  2. Open “Materialize/sass/components”.
  3. Rename “_table_of_contents.scss” to “_table-of-contents.scss”.
  4. Open “materialize.scss” in the parent directory and change the import of “components/_table_of_contents” to “components/_table-of-contents”

Try a build now at the root of parallax:

mvn clean install -PautoInstallPackage

The “ui.apps” package should be successfully installed.  Navigate to http://localhost:4502/siteadmin and create a page using the “Parallax Default Template” template.  Your page should match the following screenshot.

screencapture-localhost-4502-content-parallax-html-1466188207916

Almost perfect, but not quite …

If you look at the console in Chrome DevTools, you will see quite a few errors like the following:

Failed to load resource: the server responded with a status of 404 (Not Found)

We forgot to change the font paths … We can fix this.  Copy the “fonts” directory located under “ui.apps/src/main/content/jcr_root/etc/designs/parallax/clientlib/lib/Materialize” to the root of the clientlib folder.  We will need to change the references in the Materialize Sass files to point to this directory.  Open up “ui.apps/src/main/content/jcr_root/etc/designs/parallax/clientlib/lib/Materialize/sass/materialize.scss” and copy the contents.  Open up “ui.apps/src/main/content/jcr_root/etc/designs/parallax/clientlib/sass/main.scss” and overwrite this line:

@import ‘../lib/Materialize/sass/materialize’;

with the contents of the materialize.scss file.

Do not touch the parallax import.  Update the import references to point back the Sass files located under “Materialize/sass/components”.

Example:

@import “components/mixins";

becomes

@import "../lib/Materialize/sass/components/mixins";

Copy the “_variables.scss” file located under “ui.apps/src/main/content/jcr_root/etc/designs/parallax/clientlib/lib/Materialize/sass/components” to “ui.apps/src/main/content/jcr_root/etc/designs/parallax/clientlib/sass”.  Change reference in main.scss.

@import "../lib/Materialize/sass/materialize/components/variables";

becomes

@import "variables";

Open _variables.scss.  Update the value of $roboto-font-path to the following:

$roboto-font-path: "clientlib/fonts/" !default;

Redo the build and those 404 errors should go away.  You only needed to make a change in one place and all the font references were updated. That’s the power of a CSS Preprocessor. Any time you need to customize one of the Sass files installed by bower, you should make a copy of the file and change your references to point to the copy. If you have any issues, you can make a new copy and start over again.

A Note on Source Maps …

Inspecting an element in Chrome will open up the Chrome DevTools. The elements panel will display a link to the original Sass files and not the generated Stylesheet.

Screen-Shot-2016-06-17-at-3.18.32-PM

You can also add breakpoints to the original JavaScript files and Chrome will allow you to step through the original files instead of the minified JavaScript.

Screen-Shot-2016-06-17-at-3.28.27-PM

Note: For those who like to use VLT, VaultClipse, or AEM Developer Tools to publish their code to the JCR. You can still use these methods to publish the generated Stylesheet and Javascript files, but you must remember to run “grunt default” from a command prompt or terminal window first. It is the only way to ensure your changes have been included into the generated files.

Source code for this project can be found here.

One response to “Using Front-end Technologies like Node, Grunt, and Sass with AEM”

  1. suren says:

    Very well thought of. Thanks for the example!

Leave a Reply