Skip to content

mwanji/humpty

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 

Repository files navigation

humpty

humpty puts your web assets back together.

humpty is a library that strives to be small, understandable and easy to use. It works out of the box with WebJars for 3rd-party libraries.

humpty builds a pipeline to process assets. Customising that pipeline is often as easy as adding dependencies to your project.

Requires Java 8. humpty-servlet requires Servlet 3.

Dependency Status

Getting Started

In this example, we will bundle Jquery, underscore, Bootstrap, an application JS file and an application LESS file together into one JS file and one CSS file.

Add Dependencies

Add humpty to your dependencies:

<dependency>
  <groupId>co.mewf.humpty</groupId>
  <artifactId>humpty</artifactId>
  <version>1.0.0-SNAPSHOT</version>
</dependency>

To have humpty process requests to http://${DOMAIN}/${CONTEXT_PATH}/humpty, add humpty-servlet to your dependencies:

<dependency>
  <groupId>co.mewf.humpty</groupId>
  <artifactId>humpty-servlet</artifactId>
  <version>1.0.0-SNAPSHOT</version>
</dependency>

To compile LESS files, add humpty-less:

<dependency>
  <groupId>co.mewf.humpty</groupId>
  <artifactId>humpty-less</artifactId>
  <version>1.0.0-SNAPSHOT</version>
</dependency>

Add the following WebJars to make the JS and CSS libraries available:

<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>bootstrap</artifactId> <!-- includes Jquery transitively -->
  <version>3.3.1</version>
</dependency>
<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>underscore</artifactId>
  <version>1.4.4</version>
</dependency>

Define Bundles

Create app.js and app.less in src/main/resources/assets.

humpty uses TOML as its configuration language. Create a file called humpty.toml in src/main/resources:

"example.js" = ["jquery", "underscore", "bootstrap", "app"]
"example.css" = ["bootstrap", "app.less"]

This defines two bundles:

  • example.js which is a concatenation of jquery.js, underscore.js, bootstrap.js and app.js
  • example.css which is a concatenation of bootstrap.css and the compiled version of app.less.

Note that where the asset's file extension matches the bundle's, it can be omitted. Beware that files containing things such as ".min" must include the extension, eg. "jquery.min.js".

Out of the box, humpty handles assets located in WebJars (jquery.js, bootstrap.css, etc.) and in theassets folder (app.js and app.less).

Now we can include our concatenated and fully processed files in index.html:

<!DOCTYPE html>
<html>
  <head>
    <link href="${CONTEXT_PATH}/humpty/example.css" type="text/css" rel="stylesheet" />
  </head>
  <body>
    Hello, humpty!

    <script src="${CONTEXT_PATH}/humpty/example.js"></script>
  </body>
</html>

While developing, you may want to include files separately, for easier debugging.

For each file, add the asset's name behind the bundle's name:

<!DOCTYPE html>
<html>
  <head>
    <link href="${CONTEXT_PATH}/humpty/example.css/bootstrap.css" type="text/css" rel="stylesheet" />
    <link href="${CONTEXT_PATH}/humpty/example.css/app.less" type="text/css" rel="stylesheet" />
  </head>
  <body>
    Hello, humpty!

    <script src="${CONTEXT_PATH}/humpty/example.js/jquery.js"></script>
    <script src="${CONTEXT_PATH}/humpty/example.js/underscore.js"></script>
    <script src="${CONTEXT_PATH}/humpty/example.js/bootstrap.js"></script>
    <script src="${CONTEXT_PATH}/humpty/example.js/app.js"></script>
  </body>
</html>

and so on.

Thankfully, this can be automated. humpty-servlet adds an instance of Includes to the servlet context.

  • Get it by calling servletContext.getAttribute(Includes.class.getName())
  • Add the result of Includes#generate("example.css") and Includes#generate("example.js") to your HTML template

Processing Bundles vs. Assets

The pipeline works differently when an entire bundle is requested (example.js), as opposed to a single asset (example.js/app.js). Typically, bundles are requested when running in production and individual assets are used to make development easier.

The differences depend on what processors are running in your pipeline. Here are a few examples:

  • a compiler might produce a source map for a single asset, but not for a bundle
  • a minifier might run only for bundles
  • a linter might run only for single assets

Prepare for Production

Add the Maven plugin:

<build>
  <plugins>
    <plugin>
      <groupId>co.mewf.humpty</groupId>
      <artifactId>humpty-maven-plugin</artifactId>
      <version>1.0.0-SNAPSHOT</version>
    </plugin>
  </plugins>
</build>

Run the following from the project's root directory: mvn humpty:digest.

Digesting creates a fingerprinted file for each bundle, such as example-humpty586985585.js, which is written to the build directory (src/main/resources/META-INF/resources by default). The fingerprint will change when the bundle's content changes, so the file can be served with far-future HTTP caching headers.

In Tomcat, HTTP caching headers can be configured with a filter:

<filter>
  <filter-name>ExpiresFilter</filter-name>
  <filter-class>org.apache.catalina.filters.ExpiresFilter</filter-class>
  <init-param>
    <param-name>ExpiresDefault</param-name>
    <param-value>access plus 1 year</param-value>
  </init-param>
</filter>

<filter-mapping>
  <filter-name>ExpiresFilter</filter-name>
  <url-pattern>/webjars/*</url-pattern> <!-- In case any WebJar files are refereneced directly, for example from CSS -->
  <url-pattern>/assets/*</url-pattern> <!-- add a URL pattern if you've customised the build directory -->
  <dispatcher>REQUEST</dispatcher>
</filter-mapping>

Digesting also creates a humpty-digest.toml file that indicates that the application is in production mode. Delete it to return to development mode. In practice, this file might only exist on the source control branch from which you deploy.

In production mode, Includes#generate will link to the fingerprinted version, rather than the individual assets. Digested resources are expected to be served by the server, not by humpty.

Configuration

Global Options

Option Default Description
assetsDir "assets" The folder containing the application's assets, relative to the root of the classpath.
buildDir "src/main/resources/META-INF/resources" The root folder where assets are put after they've been through the pipeline. The default allows the assets to be served directly in environments such as Servlet 3.
[options]
  assetsDir = "assets"
  buildDir = "src/main/resources/META-INF/resources"

For example, the file ${assetsDir}/path/to/app.js would be copied to ${buildDir}/path/to/app.js.

Element-specific options

Each pipeline element has a name that can be used to set its options.

[options.element_name]
  option1 = value1
  option2 = [value2, value3, value4]

  [options.element_name.option3]
    option3_1 = value5

Pipeline Elements

humpty is a modular system that builds a pipeline composed of pipeline elements, into which bundles and assets are fed. There are several types of pipeline elements:

  • Resolvers find files
  • Processors do things to those files
  • Listeners are notified of certain events, such as when assets are processed

Bundles and Assets

A bundle is a named list of files that are accessed and processed together. The result of processing a bundle is made available at the URL defined by the name property. The name must contain a file extension (e.g. .js or .css). The bundle's contents and the order in which they are processed are set in the assets array.

By default, each asset is considered to be in a WebJar. Add WebJars to your classpath and refer to them by name in the assets array. The name can be a file name if there is no ambiguity (eg. jquery), or a longer path if the are other files with the same name, eg. smoothness/theme.css in the case of JqueryUI.

If an asset does not have an extension, the one in the name of the bundle will be used.

There are two ways of writing bundles:

# shorthand
"example.js" = ["underscore.js", "otherLib.coffee", "jquery", "myApp"]

# longhand 
["example.js"]
  assets = ["underscore.js",
            "otherLib.coffee",
            "jquery",
            "myApp"
           ]

WARNING: shorthand bundles MUST appear before any other table, or they will not work!

Resolvers

Resolvers take an asset's name and turn it into files whose contents can be read. Creating custom resolvers is discussed in the Extension Points section.

FileResolver

Bundled with humpty. Looks for files in your application's folders. The root folder is set by the global assetsDir option.

In your humpty.toml, prefix asset names with a / to indicate that it is part of your application.

Example:

"mybundle.js" = ["/myApp.js"]

WebJarResolver

Bundled with humpty. Looks up resources in a WebJar. For example, if org.webjars:jquery:2.1.1 has been added to the dependencies, the resolver will find jquery.js.

Configuration:

option type default description
preferMin boolean (auto) If true, WebJarResolver looks for a minified version of the requested asset by adding .min to the asset's base name (ie. jquery.js becomes jquery.min.js). If no such version exists or preferMin is set to false, the requested version is used. If preferMin is not set, it falls back to true in production mode and to false otherwise.
[options.webjars]
  preferMin = false

Note:

WebJars are typically in JAR files, but they can also be "faked" by reproducing the appropriate folder structure: META-INF/resources/webjars/${WEBJAR_NAME}/${WEBJAR_VERSION}/. This can be useful when a third-party library does not have a WebJar.

Processors

Processors generally modify the assets they are given in some way: compile, concatenate, minify, etc. There are 3 kinds of processors, run in the following order:

  1. SourceProcessor changes the type of the asset (eg. from asset.coffee to asset.js)
  2. AssetProcessor runs on individual assets (eg. URL rewriting, linting)
  3. BundleProcessor runs on a concatenated bundle (eg. minification)

humpty has no default processors, but they are easy to add: put the ones you want on the classpath and they are automatically added to the pipeline.

There are a number of processors available:

Creating custom processors is discussed in the Extension Points section.

Configuration Reference

By default, configuration is done via a TOML object in a file called humpty.toml at the root of the classpath. The configuration's properties are:

bundle

An array of tables which must contain at least one bundle. Each bundle has a name (required) and an array of assets (required).

# shorthand  
"libs.js" = ["jquery", "underscore"]

[[bundle]]
	name = "app.js"
	assets = ["jquery", "app.js"]

[[bundle]]
  name = "app.css"
  assets = ["bootstrap.less", "theme"]

options

Optional. A table of global and pipeline element-specific settings. The pipeline element name to use is in each element's documentation.

[options]
  mode = "DEVELOPMENT"

[options.pipeline_element_name]
  key = value

options.pipeline

Options that determine how the asset pipeline itself is created.

By default, all processors loaded via ServiceLoaders are run. For each processor type, a String array can be given to override the service loading mechanism. Each array contains the names of the processors that will run, in the given order. The options are:

  • sources
  • assets
  • bundles
  • listeners
  • bundleResolvers
[options.pipeline]
  sources = ["coffee", "emberJs"] # Only these SourceProcessors will run
  assets = [] # No AssetProcessors will run
  # All other processor types will use the service loading mechanism

Extension Points

humpty makes several interfaces available for new pipeline elements to be created, as well as a limited form of dependency injection.

Custom Pipeline Elements

New behaviour can be added to a pipeline by implementing one of PipelineElement's sub-interfaces. For them to be added to the pipeline when a user adds the JAR to their classpath, create a file called co.mewf.humpty.spi.PipelineElement in META-INF/services, containing one fully-qualified class name per line. For more information, see the JavaDoc for java.util.ServiceLoader.

The sub-interfaces are:

  • BundleResolver: finds the bundle that matches a given name
  • Resolver: provides the contents of a given asset
  • SourceProcessor
  • AssetProcessor
  • BundleProcessor
  • PipelineListener: is notified of pipeline events, but does not directly participate in the pipeline

Injection

While constructor injection is not allowed because resources must be instantiatable by a ServiceLoader, a limited form of method injection is available. One method may be annotated with the javax.inject.Inject annotation. Dependencies that can be injected:

  • Configuration is the Java representation of the entire configuration file
  • Configuration.GlobalOptions the global options
  • Configuration.Options contains the options set for the current processor
  • Pipeline the asset pipeline itself
  • WebJarAssetLocator to find assets in a WebJar, avoids pipeline elements having to create their own instance
  • any object that was added programatically to HumptyBootstrap

If a dependency cannot be satisfied, an exception is thrown.

Licensing

humpty is copyright Moandji Ezana 2013 - 2014. humpty is licensed under the MIT License.