JavaEE AngularJS : Web Packaging – Concat – Minify

logo angularjsThis tutorial is intended to make a Maven web packaging like NodeJS+Brunch or NodeJS+WebPack. With HTML to JS, Single JS file, Minify. It’s usefull for web release because you’ll to limit the number of HTTP request, have a simple artifact to update, and limit the size of your app.

Demo

A simple two pages app: 2 templates, 2 controllers.

ScreenShot009

ScreenShot010

Concat HTML files to single JS file

All HTML templates files are merged into a single JS file : template.js. Used with require.js. Limiting the number of HTTP requests done by the client (better for mobile network) and doing a better job on handling browser cache than URL to HTML templates.

Build it with the default ‘dev’ maven profile.

mvn package -P dev

ScreenShot006

Before

ScreenShot011

After

ScreenShot012

templates.js (generated)

/*jshint -W099 */
angular.module('templates-main', []).run(['$templateCache', function($templateCache) {
	$templateCache.put('/js/page1/page1.html',
	"<div class=\"page1\" ng-controller=\"Page1Ctrl\">\n" +
	"  <h2>Page 1</h2>\n" +
	"  <button ng-click=\"goToPage2()\">Go to Page 2 >></button>\n" +
	"</div>");
	$templateCache.put('/js/page2/page2.html',
	"<div class=\"page2\" ng-controller=\"Page2Ctrl\">\n" +
	"  <h2>Page 2</h2>\n" +
	"  <button ng-click=\"goToPage1()\">Go to Page 1 >></button>\n" +
	"</div>");
}]);

index_dev.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">

<!-- LIBS CSS -->
<link rel="stylesheet" href="webjars/angularjs/${angularjs.version}/angular-csp.css">

<!-- LIBS JS -->
<script src="webjars/requirejs/${requirejs.version}/require${javascript.min}.js"></script>
<script src="webjars/angularjs/${angularjs.version}/angular${javascript.min}.js"></script>
<script src="webjars/angularjs/${angularjs.version}/angular-resource${javascript.min}.js"></script>
<script src="webjars/angularjs/${angularjs.version}/angular-route${javascript.min}.js"></script>

<!-- YOUR APP -->

<!-- YOUR CSS -->
<link rel="stylesheet" href="css/page1.css?nocache=${project.version}">
<link rel="stylesheet" href="css/page2.css?nocache=${project.version}">

<!-- YOUR JS -->
<script src="js/templates.js?nocache=${project.version}"></script>
<script src="js/app.js?nocache=${project.version}"></script>
<script src="js/page1/page1.js?nocache=${project.version}"></script>
<script src="js/page2/page2.js?nocache=${project.version}"></script>

</head>
<body ng-app="myapp">
<div class="container">

  <h1>Packaging</h1>

  <!-- MAIN CONTENT -->
  <div class="view-animate" ng-view ></div>

</div>
</body>
</html>

Concat JS ans CSS Files to single Minimize file

All JS files (included templates.js) are merged into a single file. Then the same is done with CSS files. Limiting the number of HTTP requests done by the client (better for mobile network).

Build it with the ‘prod’ maven profile.

mvn package -P prod

Before

ScreenShot015

ScreenShot013

After

ScreenShot016

ScreenShot014

Warning: You’ll need to write angular files in a certain way, in order to be compatible with minification.

Before: global app var, simple injection

  • var app = angular.module…
  • app.controller…
  • …Ctrl’, function(…
var app = angular.module('myapp', [ //
'ngResource', //
'ngRoute', //
'templates-main' //
]);
...
app.controller('Page2Ctrl', function($scope, $location) {
  ...
});

Now: global module, injection with array before function

  • angular.module…
  • angular.module(…).controller…
  • …Ctrl’, [ “$scope”, …, function(…
angular.module('myapp', [ //
'ngResource', //
'ngRoute', //
'templates-main' //
]);
...
angular.module('myapp')
.controller('Page2Ctrl', [ "$scope", "$location", function($scope, $location) {
  ...
} ]);

index_prod.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">

<!-- LIBS CSS -->
<link rel="stylesheet" href="webjars/angularjs/${angularjs.version}/angular-csp.css">

<!-- LIBS JS -->
<script src="webjars/requirejs/${requirejs.version}/require${javascript.min}.js"></script>
<script src="webjars/angularjs/${angularjs.version}/angular${javascript.min}.js"></script>
<script src="webjars/angularjs/${angularjs.version}/angular-resource${javascript.min}.js"></script>
<script src="webjars/angularjs/${angularjs.version}/angular-route${javascript.min}.js"></script>

<!-- YOUR APP -->
<link rel="stylesheet" href="css/style.min.css?nocache=${project.version}">
<script src="js/script.min.js?nocache=${project.version}"></script>

</head>
<body ng-app="myapp">
<div class="container">

  <h1>Packaging</h1>

  <!-- MAIN CONTENT -->
  <div class="view-animate" ng-view ></div>

</div>
</body>
</html>

Project

ScreenShot004

Source

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.damienfremont.blog</groupId>
  <artifactId>20160522-javaee-angularjs-webpackaging_min_single_file</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>7</java.version>
    <angularjs.version>1.4.7</angularjs.version>
    <requirejs.version>2.2.0</requirejs.version>
  </properties>

  <dependencies>

    <!-- WEB DEPENDENCIES MANAGER -->
    <dependency>
      <groupId>org.webjars</groupId>
      <artifactId>webjars-servlet-2.x</artifactId>
      <version>1.1</version>
    </dependency>

    <!-- WEB DEPENDENCIES -->
    <dependency>
      <groupId>org.webjars</groupId>
      <artifactId>angularjs</artifactId>
      <version>${angularjs.version}</version>
    </dependency>
    <dependency>
      <groupId>org.webjars</groupId>
      <artifactId>requirejs</artifactId>
      <version>${requirejs.version}</version>
    </dependency>

  </dependencies>
  <build>
    <resources>

      <!-- REPLACE KEYS IN FILES (ECLIPSE IDE) -->
      <resource>
        <directory>src/main/webapp</directory>
        <filtering>true</filtering>
        <targetPath>${project.basedir}/target/m2e-wtp/web-resources</targetPath>
        <includes>
          <include>*.html</include>
          <include>WEB-INF/web.xml</include>
        </includes>
      </resource>
    </resources>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.${java.version}</source>
          <target>1.${java.version}</target>
        </configuration>
      </plugin>

      <!-- REPLACE KEYS IN FILES (BUILD) -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.6</version>
        <configuration>
          <webResources>
            <resource>
              <directory>src/main/webapp</directory>
              <filtering>true</filtering>
              <includes>
                <include>*.html</include>
                <include>WEB-INF/web.xml</include>
              </includes>
            </resource>
          </webResources>
        </configuration>
      </plugin>

      <!-- HTML TO JS (BUILD) -->
      <plugin>
        <groupId>com.keithbranton.mojo</groupId>
        <artifactId>angular-maven-plugin</artifactId>
        <version>0.3.4</version>
        <executions>
          <execution>
            <phase>generate-resources</phase>
            <goals>
              <goal>html2js</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <sourceDir>${basedir}/src/main/webapp/js/</sourceDir>
          <angularDependency>angular</angularDependency>
          <include>**/*.html</include>
          <exclude>index*.html</exclude>
          <target>${basedir}/src/main/webapp/js/templates.js</target>
          <addRequireWrapper>false</addRequireWrapper>
          <prefix>/js</prefix>
          <multiModule>false</multiModule>
          <preambles>
            <preamble>/*jshint -W099 */</preamble>
          </preambles>
        </configuration>
      </plugin>

      <!-- ASSEMBLE AND MINIFY -->
      <plugin>
        <groupId>com.samaxes.maven</groupId>
        <artifactId>minify-maven-plugin</artifactId>
        <version>1.7.4</version>
        <executions>
          <execution>
            <id>default-minify</id>
            <goals>
              <goal>minify</goal>
            </goals>
            <configuration>
              <charset>UTF-8</charset>
              <cssSourceIncludes>
                <cssSourceInclude>**/*.css</cssSourceInclude>
              </cssSourceIncludes>
              <cssSourceExcludes>
                <cssSourceExclude>**/*.min.css</cssSourceExclude>
              </cssSourceExcludes>
              <jsSourceIncludes>
                <jsSourceInclude>**/app.js</jsSourceInclude>
                <jsSourceInclude>**/template.js</jsSourceInclude>
                <jsSourceInclude>**/**/*.*js</jsSourceInclude>
              </jsSourceIncludes>
              <jsEngine>CLOSURE</jsEngine>
              <jsFinalFile></jsFinalFile>
            </configuration>
          </execution>
        </executions>
      </plugin>

    </plugins>
    <pluginManagement>
      <plugins>
        <!-- HTML TO JS (FOR ECLIPSE IDE) -->
        <plugin>
          <groupId>org.eclipse.m2e</groupId>
          <artifactId>lifecycle-mapping</artifactId>
          <version>1.0.0</version>
          <configuration>
            <lifecycleMappingMetadata>
              <pluginExecutions>
                <pluginExecution>
                  <pluginExecutionFilter>
                    <groupId>com.keithbranton.mojo</groupId>
                    <artifactId>angular-maven-plugin</artifactId>
                    <versionRange>[0.1-SNAPSHOT,)</versionRange>
                    <goals>
                      <goal>html2js</goal>
                    </goals>
                  </pluginExecutionFilter>
                  <action>
                    <execute>
                      <runOnIncremental>true</runOnIncremental>
                    </execute>
                  </action>
                </pluginExecution>
              </pluginExecutions>
            </lifecycleMappingMetadata>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
  <profiles>
    <profile>
    
      <!-- NO ASSEMBLY, NO MINIFIY, NO CACHE -->
      <id>dev</id>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
      <properties>
        <profile.id>dev</profile.id>
        <javascript.min></javascript.min>
        <webjars.disablecache>true</webjars.disablecache>
      </properties>
    </profile>
    <profile>
    
      <!-- ASSEMBLY, MINIFIY, NOCACHE -->
      <id>prod</id>
      <properties>
        <profile.id>prod</profile.id>
        <javascript.min>.min</javascript.min>
        <webjars.disablecache>false</webjars.disablecache>
      </properties>
    </profile>
  </profiles>
</project>

page1.css

.page1 {
  color: green;
}

page2.css

.page2 {
  color: orange;
}

index_dev.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">

<!-- LIBS CSS -->
<link rel="stylesheet" href="webjars/angularjs/${angularjs.version}/angular-csp.css">

<!-- LIBS JS -->
<script src="webjars/requirejs/${requirejs.version}/require${javascript.min}.js"></script>
<script src="webjars/angularjs/${angularjs.version}/angular${javascript.min}.js"></script>
<script src="webjars/angularjs/${angularjs.version}/angular-resource${javascript.min}.js"></script>
<script src="webjars/angularjs/${angularjs.version}/angular-route${javascript.min}.js"></script>

<!-- YOUR APP -->

<!-- YOUR CSS -->
<link rel="stylesheet" href="css/page1.css?nocache=${project.version}">
<link rel="stylesheet" href="css/page2.css?nocache=${project.version}">

<!-- YOUR JS -->
<script src="js/templates.js?nocache=${project.version}"></script>
<script src="js/app.js?nocache=${project.version}"></script>
<script src="js/page1/page1.js?nocache=${project.version}"></script>
<script src="js/page2/page2.js?nocache=${project.version}"></script>

</head>
<body ng-app="myapp">
<div class="container">

  <h1>Packaging</h1>

  <!-- MAIN CONTENT -->
  <div class="view-animate" ng-view ></div>

</div>
</body>
</html>

index_prod.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">

<!-- LIBS CSS -->
<link rel="stylesheet" href="webjars/angularjs/${angularjs.version}/angular-csp.css">

<!-- LIBS JS -->
<script src="webjars/requirejs/${requirejs.version}/require${javascript.min}.js"></script>
<script src="webjars/angularjs/${angularjs.version}/angular${javascript.min}.js"></script>
<script src="webjars/angularjs/${angularjs.version}/angular-resource${javascript.min}.js"></script>
<script src="webjars/angularjs/${angularjs.version}/angular-route${javascript.min}.js"></script>

<!-- YOUR APP -->
<link rel="stylesheet" href="css/style.min.css?nocache=${project.version}">
<script src="js/script.min.js?nocache=${project.version}"></script>

</head>
<body ng-app="myapp">
<div class="container">

  <h1>Packaging</h1>

  <!-- MAIN CONTENT -->
  <div class="view-animate" ng-view ></div>

</div>
</body>
</html>

app.js

'use strict';

angular.module('myapp', [ //
'ngResource', //
'ngRoute', //
'templates-main' //
])

// CONFIG
.config([ "$routeProvider", function($routeProvider) {

  // ROUTE
  $routeProvider

  // PAGE1
  .when('/page1', {
    templateUrl : '/js/page1/page1.html',
    controller : 'Page1Ctrl'
  })
  // PAGE 2
  .when('/page2', {
    templateUrl : '/js/page2/page2.html',
    controller : 'Page2Ctrl'
  })
  // DEFAULT
  .otherwise('/page1');
} ]);

page1.html

<div class="page1" ng-controller="Page1Ctrl">
  <h2>Page 1</h2>
  <button ng-click="goToPage2()">Go to Page 2 >></button>
</div>

page1.js

angular.module('myapp')

.controller('Page1Ctrl', [ "$scope", "$location", function($scope, $location) {
  $scope.goToPage2 = function() {
    $location.path('/page2');
  }
} ]);

page2.html

<div class="page2" ng-controller="Page2Ctrl">
  <h2>Page 2</h2>
  <button ng-click="goToPage1()">Go to Page 1 >></button>
</div>

page2.js

angular.module('myapp')

.controller('Page2Ctrl', [ "$scope", "$location", function($scope, $location) {
  $scope.goToPage1 = function() {
    $location.path('/page1');
  }
} ]);

web.xml

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
     http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  version="3.1">

  <servlet>
    <servlet-name>WEBJARS</servlet-name>
    <servlet-class>org.webjars.servlet.WebjarsServlet</servlet-class>
    <init-param>
      <param-name>disableCache</param-name>
      <param-value>${webjars.disablecache}</param-value>
    </init-param>
    <load-on-startup>2</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>WEBJARS</servlet-name>
    <url-pattern>/webjars/*</url-pattern>
  </servlet-mapping>

  <welcome-file-list>
    <welcome-file>index_${profile.id}.html</welcome-file>
  </welcome-file-list>

</web-app>

Conclusion

Not so easy to build. Very complex lifecycle. Not as good as NodeJS builds, but doing the job.

You’ll need it if you intend to deploy a web app. But for intranet apps, you don’t.

For big projects: use nodejs build for front-end, and maven for back-end.

Project

https://github.com/DamienFremont/blog/tree/master/20160522-javaee-angularjs-webpackaging_min_single_file

References

https://github.com/keithbranton/angular-maven-plugin

https://github.com/samaxes/minify-maven-plugin

http://maven.apache.org/plugins/maven-dependency-plugin/examples/unpacking-artifacts.html

http://stackoverflow.com/questions/35784489/angular-javascript-minification-cause-error-injectormodulerr-module-error

2 thoughts on “JavaEE AngularJS : Web Packaging – Concat – Minify

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s