-
Notifications
You must be signed in to change notification settings - Fork 22
Maven plugin for web resource optimization
- Quick introduction
- Goal of this plugin
- Source Maps
- Data URIs
- Plugin setup and simple configuration
- Dive into configuration details
- Configuration tag
- ResourcesSet tag
- Aggregation tag
- SourceMap tag
- Configuration examples
- Configuration for JAR projects
- Configuration for WAR projects
- Frequently Asked Questions
- Why I see line breaks in compressed files?
- How to preserve comments in compressed files?
- How to add a copyright or license comment to an combined file?
- Why does my JavaScript code stop working when I use ADVANCED_OPTIMIZATIONS?
- Are there cases where the optimization fails or produces a wrong result?
The Maven plugin takes advantage of Google Closure Compiler and YUI Compressor. Google Closure Compiler produces better compression results for JavaScript files than YUI Compressor. But it can not compress CSS files, so that YUI Compressor is used for that. This plugin can help you to speed up web applications by compressing and combining JavaScript and CSS resources. It's simple configurable and powerful at the same time. The plugin was tested for a lot of web applications and has produced very good results. Each single JavaScript file was better compressed with Google Closure Compiler than with any other tool, even in simple mode (see compilation level below). There are many similar tests in the Internet. In advanced mode the difference was dramatic. But be careful by using of advanced mode by reason of some restrictions like dead code removal. Follow recommendations for Closure Compiler please to avoid unwanted surprises.
The plugin shows statistic in the output console, e.g.
[INFO] === Statistic ===========================================
[INFO] Size of original resources = 2.709 MB
[INFO] Size of optimized resources = 1.307 MB
[INFO] Optimized resources have 48.23% of original size
[INFO] =========================================================
Another goal and feature of this Maven plugin is creation of Source Maps and handling of Data URIs.
If you're doing front end web development, your web resources such as JavaScript and CSS files might be minificated, transpiled or compiled from a completely different language. If you now want to debug the generated code in the browser, you can not do that because the output code is obfuscated from the code you wrote. The solution is to use source maps. A good introduction to the source map can be found in the articles Introduction to JavaScript Source Maps and Enhance Your JavaScript Debugging with Cross-Browser Source Maps. Currently, the plugin only supports the creation of source maps for JavaScript files. If your generated code is JavaScript, one way to let the development tools know where to look is to add a comment to the end of the generated code which defines the sourceMappingURL - the location of the source map. For example: //# sourceMappingURL=mylib.js.map
or //# sourceMappingURL=/mypath/mylib.js.map
or //# sourceMappingURL=http://sourcemaps/mylib.js.map
If you now open a development tool and the source map support is enabled, the browser will stream down the source map which points to the original file and show the original file instead of generated one (minificated, compiled, transpiled, etc.). Now, you can set a breakpoint in the original file and debug it as it would be delivered with you web application. Such debugging is possible due to the fact that a source map provides a way of mapping code within a generated file back to it's original position in a source file.
Data URIs are an interesting concept on the Web. Read "Data URIs explained" please if you don't know what it does mean. Data URIs allow any file to be embedded inline within CSS. This technique allows separate elements such as images to be fetched in a single HTTP request rather than multiple HTTP requests, what can be more efficient. Decreasing the number of requests results in better page performance. "Minimize HTTP requests" is actually the first rule of the "Yahoo! Exceptional Performance Best Practices", and it specifically mentions data URIs. The plugin allows to embed data URIs for referenced images in style sheets. Data URIs are supported for all modern browsers: Gecko-based (Firefox, SeaMonkey, Camino, etc.), WebKit-based (Safari, Google Chrome), Opera, Konqueror, Internet Explorer 8 and higher. For Internet Explorer 8 data URIs must be smaller than 32 KB. Internet Explorer 9 and higher don't have this 32 KB limitation. How does the conversion to data URIs work?
Plugin reads the content of CSS files. A special java.io.Reader
implementation looks for tokens #{resource[...]}
in CSS files. This is a syntax for image references in JSF 2. Token should start with #{resource[
and ends with ]}
. The content inside contains image path in JSF syntax. Examples:
.ui-icon-logosmall {
background-image: url("#{resource['images/logosmall.gif']}") !important;
}
.ui-icon-aristo {
background-image: url("#{resource['images:themeswitcher/aristo.png']}") !important;
}
In the next step the image resource for each background image is localized. Images directories are specified according to the JSF 2 specification and suit WAR as well as JAR projects. These are ${project.basedir}/src/main/webapp/resources
and ${project.basedir}/src/main/resources/META-INF/resources
. Every image is tried to be found in those directories.
If the image is not found in the specified directories, then it doesn't get transformed. Otherwise, the image is encoded into base64 string. The encoding is performed only if the data URI string is less than 32KB in order to support IE8 browser. Images larger than that amount are not transformed. Data URIs looks like
.ui-icon-logosmall {
background-image: url("data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgA ... ASUVORK5CYII=") !important;
}
.ui-icon-aristo {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA ... BJRU5ErkJggg==") !important;
}
Supported mime-types are: image/gif
, image/jpeg
and image/png
, so that GIF, JPEG, JPG and PNG images can be converted to data URIs.
Maven plugin for resource optimization is available in the Maven Central repository. You can use it by adding the following dependency to your pom.xml:
<build>
<plugins>
...
<plugin>
<groupId>org.primefaces.extensions</groupId>
<artifactId>resources-optimizer-maven-plugin</artifactId>
<version>2.6.2</version>
<executions>
<execution>
<id>optimize</id>
<goals>
<goal>optimize</goal>
</goals>
</execution>
</executions>
</plugin>
...
</plugins>
</build>
The last released version can be determined by searching in the Maven Central repository. There is a default plugin configuration described in the next section, but you need a proper configuration to be able to use this plugin rationally. A simple configuration would be as follows:
<plugin>
<groupId>org.primefaces.extensions</groupId>
<artifactId>resources-optimizer-maven-plugin</artifactId>
<version>2.6.2</version>
<executions>
<execution>
<id>optimize</id>
<goals>
<goal>optimize</goal>
</goals>
</execution>
</executions>
<configuration>
<inputDir>${project.build.directory}/your_resource_directory</inputDir>
</configuration>
</plugin>
And that's all in the simplest case. In this case only an input directory is specified where resources are located. JavaScript and CSS files located below this directory and its sub directories will be compressed and overwrite the existing not compressed files at the same location. This is a default behavior. Compilation level for Closure Compiler is predefined as SIMPLE_OPTIMIZATIONS
and warning level is predefined as QUIET
. Please refer API Reference for more details.
The more complex use case is shown below.
<plugin>
<groupId>org.primefaces.extensions</groupId>
<artifactId>resources-optimizer-maven-plugin</artifactId>
<version>2.6.2</version>
<executions>
<execution>
<id>optimize</id>
<goals>
<goal>optimize</goal>
</goals>
</execution>
</executions>
<configuration>
<compilationLevel>ADVANCED_OPTIMIZATIONS</compilationLevel>
<warningLevel>VERBOSE</warningLevel>
<failOnWarning>true</failOnWarning>
<suffix>-min</suffix>
<useDataUri>true</useDataUri>
<languageIn>ECMASCRIPT_2015</languageIn>
<languageOut>ECMASCRIPT3</languageOut>
<resourcesSets>
<resourcesSet>
<inputDir>${project.build.directory}/your_resource_directory_for_js_files</inputDir>
<includes>
<include>**/*.js</include>
</includes>
<excludes>
<exclude>**/jquery.layout.js</exclude> // example of file exclude
<exclude>jquery/**</exclude> // example of directory exclude
</excludes>
</resourcesSet>
<resourcesSet>
<inputDir>${project.build.directory}/your_resource_directory_for_css_files</inputDir>
<includes>
<include>**/*.css</include>
</includes>
<aggregations>
<aggregation>
<outputFile>${project.build.directory}/webapp/resources/${project.artifactId}.css</outputFile>
</aggregation>
</aggregations>
</resourcesSet>
</resourcesSets>
</configuration>
</plugin>
This sample shows how to overwrite default configuration and work with resources sets. You can overwrite default settings: input directory, compilation level, warning level, behavior in case of warnings, file encoding, files to be included / excluded, aggregation rules, javascript language specifications and desired suffix for minified resources. See the next section for more details. Resources sets are optional and allows more configuration control. In the example above we have two resources sets. One for JavaScript files and one for CSS files. Each resources set can overwrite (redefine) settings for input directory where files are located, compilation level, warning level, includes, excludes and aggregation rules. The first resourcesSet
includes JavaScript files except jQuery files which are usally already minified and can't be compressed with ADVANCED_OPTIMIZATIONS
at the moment. Therefore, they are excluded. You can exclude single files or entire directories. The second resourcesSet
compresses CSS files and merges them to one file according to the tag outputFile
. Merging to one big file is possible from different resourcesSet
as well. Merged content is always appended to an output file if the file already exists. The configuration above also shows how to enable data URI feature. To enable this feature set useDataUri
flag to true
.
The next example demonstrates simple settings for source map configuration.
<plugin>
<groupId>org.primefaces.extensions</groupId>
<artifactId>resources-optimizer-maven-plugin</artifactId>
<version>2.6.2</version>
<configuration>
<sourceMap>
<create>true</create>
<outputDir>${project.basedir}/src/sourcemap/${project.version}</outputDir>
<sourceMapRoot>
https://raw.githubusercontent.com/someproject/master/src/sourcemap/${project.version}/
</sourceMapRoot>
</sourceMap>
</configuration>
...
</plugin>
That means, a compressed file, say timeline.js
, has the following line at the end:
//# sourceMappingURL=https://raw.githubusercontent.com/someproject/master/src/sourcemap/3.2.0/timeline.js.map
The source map timeline.js.map
has the content:
{
"version":3,
"file":"timeline.js",
"lineCount":238,
"mappings":"A;;;;;;;;;;;;;;;;;;;;AA4DqB,WAArB,GAAI,MAAOA,MAAX,GACIA,KADJ,CACY,EADZ,CAUsB,...
"sources":["timeline.source.js"],
"names":["links","google","undefined","Array","prototype","indexOf","Array.prototype.indexOf",...]
}
If the browser development tools has identified that the source map is available, it will be fetched along with referenced uncompressed source file timeline.source.js
. The source file appears in the development tools, you can set breakpoint(s) and debug it. Note: the value of the create
tag can be set to false
for development and to true
for release via a Maven property.
The plugin is highly configurable. The following table gives an overview about all configuration details inside of configuration
tag. It doesn't cover the configuration of resourcesSet
.
Tag | Type | Default value | Description |
---|---|---|---|
skip | Boolean | false | Skip this plugin if the parameter is true |
inputDir | File | ${project.build.directory}/webapp | Input directory. Command-line parameter is "inputDir". |
compilationLevel | String | SIMPLE_OPTIMIZATIONS | Compilation level. Possible values are: WHITESPACE_ONLY, SIMPLE_OPTIMIZATIONS, ADVANCED_OPTIMIZATIONS. Command-line parameter is "compilationLevel". |
warningLevel | String | QUIET | Warning level. Possible values are: QUIET, DEFAULT, VERBOSE. To understand errors and warnings see this reference. Command-line parameter is "warningLevel". |
encoding | String | UTF-8 | Encoding to read files. Note: output files are ASCII encoded to avoid issues with proxy-servers. Command-line parameter is "encoding". |
failOnWarning | boolean | false | Flag whether this plugin must stop/fail on warnings. |
suffix | String | null | Suffix for compressed / aggregated files. |
useDataUri | boolean | false | Flag if images referenced in CSS files should be converted to data URIs. |
includes | String[] | {"/*.css", "/*.js"} | Files to be included. Files selectors follow patterns specified in org.codehaus.plexus.util.DirectoryScanner. |
excludes | String[] | empty array | Files to be excluded. Files selectors follow patterns specified in org.codehaus.plexus.util.DirectoryScanner. |
aggregations | Aggregation[] | null | Aggregations describing configurations how the files have to be aggregated to one big file (outputFile mode, s. below) or less files (subDirMode mode, s. below). |
sourceMap | SourceMap | null | Configuration of the source map for all resource sets. See more details below. |
resourcesSets | List<ResourcesSet> | null | List of resource sets describing resources to be processed (s. below) |
languageIn | String | ECMASCRIPT3 | Sets what language spec that input javascript sources conform. Options: ECMASCRIPT3, ECMASCRIPT5,ECMASCRIPT5_STRICT, ECMASCRIPT_2015, ECMASCRIPT6_TYPED, ECMASCRIPT_2016,ECMASCRIPT_2017, ECMASCRIPT_2018, ECMASCRIPT_2019, ECMASCRIPT_2020, ECMASCRIPT_2021, ECMASCRIPT_NEXT,ECMASCRIPT_NEXT_IN, STABLE, NO_TRANSPILE |
languageOut | String | NO_TRANSPILE | Sets what language spec the output javascript should conform to. Options: ECMASCRIPT3,ECMASCRIPT5, ECMASCRIPT5_STRICT, ECMASCRIPT_2015, ECMASCRIPT6_TYPED, ECMASCRIPT_2016,ECMASCRIPT_2017, ECMASCRIPT_2018, ECMASCRIPT_2019, ECMASCRIPT_2020, ECMASCRIPT_2021, ECMASCRIPT_NEXT,ECMASCRIPT_NEXT_IN, STABLE, NO_TRANSPILE |
emitUseStrict | boolean | false | Flag whether to add 'use strict' to JS files. |
processCommonJSModules | boolean | false | Flag rewrites CommonJS modules so that modules can be concatenated together, by renaming all globals to avoid conflicting with other modules. |
The next table gives an overview about all configuration details inside of resourcesSet
tag. resourcesSet
contains of couple of the identical configuration parameters as in configuration
tag. Consider the following rules please.
- If the same parameter was configured in
configuration
directly, the value of this parameter inresourcesSet
takes precedence (overwrites the corresponding value inconfiguration
). - If a parameter is missing in
resourcesSet
, the value of the corresponding parameter inconfiguration
will be taken into account.
Tag | Type | Default value | Description |
inputDir | File | null | Input directory for this resource set. |
compilationLevel | String | null | Compilation level. Possible values are: WHITESPACE_ONLY, SIMPLE_OPTIMIZATIONS, ADVANCED_OPTIMIZATIONS. |
warningLevel | String | null | Warning level. Possible values are: QUIET, DEFAULT, VERBOSE. To understand errors und warnings see this reference. |
useDataUri | boolean | false | Flag if images referenced in CSS files should be converted to data URIs. |
includes | String[] | null | Files to be included. Files selectors follow patterns specified in org.codehaus.plexus.util.DirectoryScanner. |
excludes | String[] | null | Files to be excluded. Files selectors follow patterns specified in org.codehaus.plexus.util.DirectoryScanner. |
aggregations | Aggregation[] | null | Aggregations describing configurations how the files have to be aggregated to one big file (outputFile mode, s. below) or less files (subDirMode mode, s. below). |
sourceMap | SourceMap | null | Configuration of the source map for this resource set. See more details below. |
The next table gives an overview about all configuration details inside of aggregation
tag. The rules for duplicated / missing configuration parameters are:
- If a parameter is missing in
aggregation
, the value of the corresponding parameter inresourcesSet
will be taken into account. - If a parameter is missing in
resourcesSet
, the value of the corresponding parameter inconfiguration
will be taken into account. - If the same parameter was configured in
resourcesSet
andconfiguration
, the value of this parameter inresourcesSet
takes precedence.
Tag | Type | Default value | Description |
inputDir | File | null | Input directory for this aggregation. This allows to define different directories to aggregate compressed and not compressed files simultaneously, at the same time. This makes sense if you have "production" and "development" modes and would like to use uncompressed files in the "development" mode. Uncompressed files can be better debugged and produce better error messages. See the next section with examples how to do such configuration. |
subDirMode | boolean | false | Aggregation per sub-folder. Names of aggregated files should be the same as their folder names where they are placed. Files are aggregated in the lexicographic order. If you want to aggregate files in the pre-defined order, you shoud give all files proper names. It's a good practice to use prefixes to sort them lexicographically. E.g. 0-firstfile.js, 1-seconfile.js, 2-thirdfile.js. See the next section with examples how to do such configuration. |
removeIncluded | boolean | true | Flag whether included original files must be removed. |
withoutCompress | boolean | false | Flag whether included files must be compressed or not. |
outputFile | File | null | Output file for aggregation to one big file. |
prependedFile | File | null | File to be prepended to the aggregated file. |
subDirMode
is probably one of the most benefits of this plugin for modular software. Resources of each module can be optimized separately in an easy way. It makes sense e.g. for component / widget development. Developers can write their scripts / cascading style sheets well-arranged and as much as they want (for better maintenance) - all files will be combined during project build. Web pages should reference combined files from the start of course.
The sourceMap
tag consists of 5 possible sub tags which are listed below. The rules for duplicated / missing configuration parameters are the same as described above:
- If a parameter is missing in
sourceMap
configured forresourcesSet
, the value of the corresponding parameter inconfiguration
will be taken into account. - If the same parameter was configured in
resourcesSet
andconfiguration
, the value of this parameter inresourcesSet
takes precedence.
Tag | Type | Default value | Description |
create | boolean | false | Boolean flag if the source map should be created. |
sourceMapRoot | String | null | Path to the location of source maps and original source files. The path is prepended to the file name in the source mapping URL declaration //# sourceMappingURL which is appended to all minified files of corresponding resource set. The default value null means nothing will be prepended. |
outputDir | String | ${project.build.directory}/sourcemap/ | Output directory for created source maps and original source files. |
detailLevel | String | V3 | Source maps details level as Enum name. See com.google.javascript.jscomp.SourceMap.DetailLevel. |
format | String | ALL | Source maps format as Enum name. See com.google.javascript.jscomp.SourceMap.Format. |
Configuration examples demonstrate several common use cases.
If you provide web resources in a JAR file as e.g. JSF component libraries typically do, you can configure this plugin as shown below.
<configuration>
<inputDir>${project.build.directory}/classes/META-INF/resources/primefaces</inputDir>
<resourcesSets>
<resourcesSet>
<inputDir>${project.build.directory}/classes/META-INF/resources</inputDir>
</resourcesSet>
<resourcesSet>
<includes>
<include>jquery/ui/jquery-ui.css</include>
<include>accordion/accordion.css</include>
...
<include>treetable/treetable.css</include>
<include>wizard/wizard.css</include>
</includes>
<aggregations>
<aggregation>
<withoutCompress>true</withoutCompress>
<outputFile>
${project.build.directory}/classes/META-INF/resources/primefaces/primefaces.css
</outputFile>
</aggregation>
</aggregations>
</resourcesSet>
<resourcesSet>
<includes>
<include>core/core.js</include>
<include>accordion/accordion.js</include>
...
<include>treetable/treetable.js</include>
<include>wizard/wizard.js</include>
</includes>
<aggregations>
<aggregation>
<withoutCompress>true</withoutCompress>
<outputFile>
${project.build.directory}/classes/META-INF/resources/primefaces/primefaces.js
</outputFile>
</aggregation>
</aggregations>
</resourcesSet>
</resourcesSets>
</configuration>
The next example demonstrates how the configuration for PrimeFaces Extensions project was made in the past. This configuration uses subDirMode
, withoutCompress
parameters and prepares compressed and uncompressed resources. Such preparation takes place by using of maven-resources-plugin which is placed in front of resources-optimizer-maven-plugin. A special JSF ResourceHandler could stream down uncompressed resources to the browser for the ProjectStage "Development".
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${resources.dir.uncompressed}</outputDirectory>
<resources>
<resource>
<directory>
${project.basedir}/src/main/resources/META-INF/resources/primefaces-extensions
</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.primefaces.extensions</groupId>
<artifactId>resources-optimizer-maven-plugin</artifactId>
<configuration>
<resourcesSets>
<resourcesSet>
<includes>
<include>imageareaselect/**</include>
<include>layout/**</include>
<include>tooltip/**</include>
<include>keyfilter/**</include>
</includes>
<aggregations>
<aggregation>
<inputDir>${resources.dir.compressed}</inputDir>
<subDirMode>true</subDirMode>
</aggregation>
<aggregation>
<withoutCompress>true</withoutCompress>
<inputDir>${resources.dir.uncompressed}</inputDir>
<subDirMode>true</subDirMode>
</aggregation>
</aggregations>
</resourcesSet>
<resourcesSet>
<includes>
<include>masterdetail/masterdetail.css</include>
</includes>
<aggregations>
<aggregation>
<inputDir>${resources.dir.compressed}</inputDir>
<outputFile>${resources.dir.compressed}/primefaces-extensions.css</outputFile>
</aggregation>
<aggregation>
<withoutCompress>true</withoutCompress>
<inputDir>${resources.dir.uncompressed}</inputDir>
<outputFile>${resources.dir.uncompressed}/primefaces-extensions.css</outputFile>
</aggregation>
</aggregations>
</resourcesSet>
<resourcesSet>
<includes>
<include>core/core.js</include>
<include>ajaxstatus/ajaxstatus.js</include>
<include>ckeditor/widget.js</include>
<include>imagerotateandresize/imagerotateandresize.js</include>
</includes>
<aggregations>
<aggregation>
<inputDir>${resources.dir.compressed}</inputDir>
<outputFile>${resources.dir.compressed}/primefaces-extensions.js</outputFile>
</aggregation>
<aggregation>
<withoutCompress>true</withoutCompress>
<inputDir>${resources.dir.uncompressed}</inputDir>
<outputFile>${resources.dir.uncompressed}/primefaces-extensions.js</outputFile>
</aggregation>
</aggregations>
</resourcesSet>
</resourcesSets>
</configuration>
</plugin>
<properties>
<resources.dir.compressed>${project.build.directory}/classes/META-INF/resources/primefaces-extensions</resources.dir.compressed>
<resources.dir.uncompressed>${project.build.directory}/classes/META-INF/resources/primefaces-extensions-uncompressed</resources.dir.uncompressed>
</properties>
We prepared three sample configurations. Standard WAR packaging does not expose web resources under src/main/webapp
for use in other plugins and runs as a single execution. Fortunately, we can use maven-resources-plugin to copy these resources earlier in the build lifecycle, so that they can be used by the resources-optimizer-maven-plugin. The standard package usage will use then copied (modified) web resources when creating the WAR artifact. Run maven-resources-plugin with phase generate-resources
before resources-optimizer-maven-plugin (phase prepare-package
). A typically configuration for web projects with generated WAR files can be done with the following steps.
<build>
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/generated-webapp/resources</outputDirectory>
<resources>
<resource>
<directory>${project.basedir}/src/main/webapp/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.css</include>
<include>**/*.js</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.primefaces.extensions</groupId>
<artifactId>resources-optimizer-maven-plugin</artifactId>
<executions>
<execution>
<id>optimize</id>
<phase>prepare-package</phase>
<goals>
<goal>optimize</goal>
</goals>
</execution>
</executions>
<configuration>
<resourcesSets>
<resourcesSet>
<inputDir>${project.build.directory}/generated-webapp</inputDir>
</resourcesSet>
</resourcesSets>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<executions>
<execution>
<id>war</id>
<phase>package</phase>
<goals>
<goal>war</goal>
</goals>
</execution>
</executions>
<configuration>
<webappDirectory>${project.build.directory}/webapp</webappDirectory>
<warSourceDirectory>${project.basedir}/src/main/webapp</warSourceDirectory>
<warSourceExcludes>**/resources/**/*.css,**/resources/**/*.js</warSourceExcludes>
<webResources>
<resource>
<directory>${project.build.directory}/generated-webapp</directory>
</resource>
</webResources>
</configuration>
</plugin>
</plugins>
</build>
In the example above we set warSourceDirectory
for the maven-war-plugin to ${project.build.directory}/generated-webapp
because this is a working directory for the resources-optimizer-maven-plugin. Many plugins works in the same manner when building WARs. You can see this e.g. for maven-replacer-plugin. The second example shows an advanced configuration with resource aggregation.
<build>
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/webapp-resources/resources</outputDirectory>
<resources>
<resource>
<directory>${project.basedir}/src/main/webapp/resources</directory>
<includes>
<include>**/*.css</include>
<include>**/*.js</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.primefaces.extensions</groupId>
<artifactId>resources-optimizer-maven-plugin</artifactId>
<executions>
<execution>
<id>optimize</id>
<phase>prepare-package</phase>
<goals>
<goal>optimize</goal>
</goals>
</execution>
</executions>
<configuration>
<resourcesSets>
<resourcesSet>
<inputDir>${project.build.directory}/webapp-resources/resources/css</inputDir>
<includes>
<include>**/*.css</include>
</includes>
<aggregations>
<aggregation>
<outputFile>
${project.build.directory}/webapp/resources/css/${project.artifactId}.css
</outputFile>
</aggregation>
</aggregations>
</resourcesSet>
<resourcesSet>
<inputDir>${project.build.directory}/webapp-resources/resources/js</inputDir>
<includes>
<include>**/*.js</include>
</includes>
<aggregations>
<aggregation>
<outputFile>
${project.build.directory}/webapp/resources/js/${project.artifactId}.js
</outputFile>
</aggregation>
</aggregations>
</resourcesSet>
</resourcesSets>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<executions>
<execution>
<id>war</id>
<phase>package</phase>
<goals>
<goal>war</goal>
</goals>
</execution>
</executions>
<configuration>
<warSourceDirectory>${project.basedir}/src/main/webapp</warSourceDirectory>
<warSourceExcludes>**/resources/**/*.css,**/resources/**/*.js</warSourceExcludes>
</configuration>
</plugin>
</plugins>
</build>
The third example only shows a configuration part for advanced techniques. You see there how to overwrite default settings for compilationLevel
, warningLevel
in a single resourcesSet
and how to use prependedFile
tag (see also FAQ).
<configuration>
<inputDir>${project.build.directory}/classes/META-INF/resources/primefaces-extensions</inputDir>
<failOnWarning>true</failOnWarning>
<suffix>-min</suffix>
<excludes>
<exclude>**/jquery.layout.js</exclude>
<exclude>jquery/**</exclude>
</excludes>
<aggregations>
<aggregation>
<subDirMode>true</subDirMode>
<removeIncluded>false</removeIncluded>
</aggregation>
</aggregations>
<resourcesSets>
<resourcesSet>
<inputDir>${project.build.directory}/webapp/resources/themes/app</inputDir>
<includes>
<include>**/*.css</include>
</includes>
<aggregations>
<aggregation>
<withoutCompress>true</withoutCompress>
<outputFile>
${project.build.directory}/webapp/resources/themes/app/${project.artifactId}.css
</outputFile>
</aggregation>
</aggregations>
</resourcesSet>
<resourcesSet>
<inputDir>${project.build.directory}/webapp/resources/js/app</inputDir>
<compilationLevel>ADVANCED_OPTIMIZATIONS</compilationLevel>
<warningLevel>DEFAULT</warningLevel>
<includes>
<include>**/*.js</include>
</includes>
<aggregations>
<aggregation>
<prependedFile>
${project.build.directory}/webapp/resources/js/app/license.txt
</prependedFile>
<outputFile>
${project.build.directory}/webapp/resources/js/app/${project.artifactId}.js
</outputFile>
</aggregation>
</aggregations>
</resourcesSet>
</resourcesSets>
</configuration>
Firewalls and proxies sometimes corrupt or ignore large CSS and JavaScript files with very long lines. This Maven plugin adds line breaks every 500 characters in order to prevent this problem. The impact on code size is small. The code size penalty is even smaller when files are gzipped.
All comments are removed from the files by default but often it's necessary to preserve copyright and license terms of use etc. There is a simple solution to retain comments in files. To preserve comments in CSS file, simply add an !
sign after the opening \*
and the comment block will remain in the compressed output.
/*! some copyright information here */
To preserve comments in JavaScript file, simply add an @license
or @preserve
annotation and the comment block will remain in the compressed output.
/**
* @preserve Copyright 2015 SomeThirdParty.
* Here is the full license text and copyright notice for this file.
*/
If you use an aggregation tag, you can prepend any other file containing some comments to the output file with the tag prependedFile
. By means of this tag you are able to insert any comments at the beginning of the combined output file.
<resourcesSet>
...
<aggregation>
<prependedFile>${project.build.directory}/webapp/resources/js/app/license.txt</prependedFile>
<outputFile>${project.build.directory}/webapp/resources/js/application.js</outputFile>
</aggregation>
...
</resourcesSet>
Using Advanced mode usually requires some preparation and code changes. Advanced Compilation and Externs explains how to make sure your code works with ADVANCED_OPTIMIZATIONS
. You will probably see warnings or errors as well if you use warning levels like DEFAULT
or VERBOSE
. The Google Closure Compiler validates JavaScript code. E.g. if you declare a global variable without the keyword var
, you will see a warning JSC_UNDEFINED_VARIABLE
. Warning level can be set in the plugin configuration by the tag warningLevel
.
There are some rare cases causing problems. Google Closure Compiler has a well-known issue with eval()
method. eval()
is EVIL :-), we know, but many "old" scripts still uses this construct. This code e.g.
var node = getNode();
eval('myHandler(node)');
will not work after optimization because the variable node
gets optimized (is shorten) and the string myHandler(node)
stays unchanged. Constructs like
eval('a.' + b)
are problematic as well. Sure, writing a[b]
would solve the problem, but we can not do that for third-party libraries. Better would be to use the same approach applied by UglifyJS. A good approach would be: "If eval()
or with{}
are used in some scope, then all variables in that scope and any variables in the parent scopes will remain unmangled, and any references to such variables remain unmangled as well."
YUI Compressor has troubles with some bizarre constructs as well. We have figured out e.g. that using of comments and a well-known Internet Explorer hack with *
sign together stops the plugin's running. This CSS snippet leads to the problem and breaks the optimization:
/* float clearing for IE6 */
* html .ui-module {
height: 1%;
}
/* float clearing for IE7 */
* + html .ui-module {
min-height: 1%;
}
A solution is simple - remove comments /* .... */
.