Better Spring management of Javascript resources with Bakehouse

Bakehouse is a new project I’ve put together in an attempt to help with the workflow of writing web-apps with Spring.

Spring itself does a bunch of stuff right.  Specifically, with the new @Profile support introduced in 3.1, it’s become very easy to generate a single WAR that can be distributed in all environments (Dev, QA, Production, etc).

This is great server-side.  However, as more and more code is moved to the client-side, different frameworks and practices are emerging that require pre-processing of resources.

For example, on a typical app you might:

  • Be compiling your CSS with LessCSS or SASS
  • Concatenate and Minify your JSS when moving into production
  • Use a javascript abstraction such as Typescript or Coffeescript that require compiling
  • Use local versions of 3rd party libs (eg., JQuery) during development, but the CDN hosted version in production

Typically, these tasks require different compilation options, depending on where you’re deploying to.   This is at odds with Spring’s goal of a single distributable.

Additionally, depending on your toolset, as you’re coding you’ll need to recompile your assets down to javascript, to use them.  This adds friction to the development process.

That’s where Bakehouse comes in.

Bakehouse allows you to define the resources in your jsp, and provide different sets of processors depending on the active profile.

For example:

<%@ taglib prefix="bakehouse" uri="http://www.mangofactory.com/bakehouse" %>
<head>
    <bakehouse:resource src="angular.js" cdn="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular.min.js"/>
    <bakehouse:resources configuration="javascript" type="text/javascript">
        <bakehouse:resource src="file1.js"/>
        <bakehouse:resource src="file2.js"/>
    </bakehouse:resources>
    <bakehouse:resources configuration="typescript" type="text/javascript">
        <bakehouse:resource src="typescript.ts"/>
    </bakehouse:resources>
</head>

This defines the resources that our page will use.  Next, wire up a configuration that tells the page how to process them.  The configuration itself can vary between profiles.

@Configuration
@Profile("Production")
public class ExampleBakehouseConfig implements BakehouseConfigProvider {

    @Override @Bean
    public BakehouseConfig build(BakehouseConfigBuilder builder) {
            return builder
                .process("javascript").serveAsSingleFile("AppCode.js")
                .process("typescript").with(new TypescriptProcessor("TypescriptCode.js"))
                .serveResourcesFromCdn()
                .build();
        }
 }

Now, in Production, the following is generated:

<head>
    <script src='http://ajax.googleapis.com/ajax/libs/angularjs/1.0.3/angular.min.js' type='text/javascript'></script>
    <script src='/bakehouse-example/generated/AppCode.js' type='text/javascript'></script>
    <script src='/bakehouse-example/generated/TypescriptCode.js' type='text/javascript'></script>
</head>

However, perhaps in development, you’d rather use a local version of AngularJs (that hasn’t been minified), and you’d rather not have your javascript concatenated.

Simple – just define another @Profile:

@Configuration
@Profile("development")
public class ExampleBakehouseConfig implements BakehouseConfigProvider {

    @Override @Bean
    public BakehouseConfig build(BakehouseConfigBuilder builder) {
        return builder
            .process("typescript").with(new TypescriptProcessor("TypescriptCode.js"))
            .build();
    }
}

Now, the default processing is applied to javascript resources, (which means CDN’s are ignored), and only our Typescript is processed.

Bakehouse is intended to be extensible – making it easy to write your own processors. Currently, there’s only support for Javscript concatenation and Typescript processing. However,more is planned, and implementing your own is easy.

Reducing development friction

Bakehouse monitors resources declared, and recompiles them whenever the underlying source changes.  This removes the need to drop out of Eclipse to recompile your LessCSS, Coffescript or Typescript (for example).

The resulting files are cached, to ensure that they’re served speedily.

Getting started

Kicking things off is simple.  In your Spring config, just ensure that is enabled, and declare a BakehouseSupport bean:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

    <context:annotation-config />
    <context:component-scan base-package="com.mangofactory.bakehouse.example" />
    <bean class="com.mangofactory.bakehouse.config.BakehouseSupport" />
</beans>

There’s a working example project available here.

Wanna see more?  Tell me!

Bakehouse is currently a POC project.  I’m using it internally on a project, but if you’re interested in seeing open source development continue, you’ll need to let me know.  Either star the project on Github, or leave a note on this issue.

Advertisements

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

%d bloggers like this: