Porting Jasper to FW/1

code jasper cfml
August 04, 2021 / Robert Zehnder

Examining the process of porting Jasper from ColdBox to FW/1

Cover

TL;DR

I am working on jasper-fw1, a CF static site generator using FW/1.

In the beginning

Jasper has become one of my favorite projectss because it continually provides me the opportunity to learn new things. I first started Jasper because I no longer wanted to host my blog on the Ghost platform. Do not get me wrong, Ghost is awesome, but it is overkill for what I need. I thought it would be cool to be able to curate content with markdown files and eliminate the need for a database. When asked if Jasper could provide SSG functionality, I got that functioning. It has all been down hill from there.

Moving to FW/1

I will be the first to admit, I love Ortus stuff. If I need to pound out some cfml quickly, install dependencies, or write a new blog post I am doing it in a commandbox instance. Whether I am doing and API integration or working on a side project, I am doing it in ColdBox. I find using *box products very comfortable; it is very easy to scaffold an application and get on my way.

I like to try to push myself a little, so I thought it might be interesting to see how I would code Jasper if I tried something I was not so comfortable with. I was looking at ways to condense Jasper's code base and FW/1 looked to be a great place to start. FW/1 consists of one file so it is easy to install: box install fw1.

I am still using CommandBox to host the server. so I enable URL rewrites useing server.json.

{
    "web":{
        "rewrites":{
            "enable":"true"
        }
    }
}

Configuring the Application

One of the really awesome aspects of FW/1 is how easy it is to configure using the variables.framework structure in Application.cfc. Some times I would get lost trying to remember where a setting was located in ColdBox, that is not a problem here. The routes and their respective handlers are set.

The javaLoaderFactory instance will be created when the application is loaded. This will make sure javaLoader instances are created at the server level as suggested by the documentation.

// Application.cfc
component extends=framework.one {
    this.name = hash( getCurrentTemplatePath() );
    variables.framework = {
        generateSES: true,
        SESOmitIndex: true,
        routesCaseSensitive: false,
        reloadApplicationOnEveryRequest: true,
        routes: [
			{ "/post/:slug": "/main/getPost/slug/:slug" },
			{ "/page/:slug": "/main/getPage/slug/:slug" },
			{ "/tag/:tag": "/main/byTag/tag/:tag" },
			{ "/search": "/main/search" }
        ]
    };
 
    function setupApplication () {
        application.javaLoaderFactory = new JavaLoaderFactory();
    }
}

Neat and tidy.

Jasper requires a few Java libraries to function. Flexmark markdown parser is used to transform markdown to hmtl, snakeyaml is used to parse the the YAML frontmatter from the markdown files, and jSoup is used to build the text data used by Lunr search. I have gathered all the required jars and placed them in the /lib folder. The jars are passed to javaloader as loadPaths argument it is created in the service.

As an example, here is the basic jSoup service used to parse HTML.

// model/services/jsoup.cfc
component {
	function init () {
        variables.javaloader = application.javaLoaderFactory.getJavaLoader([
            expandPath("/lib/jsoup-1.12.1.jar")
        ]);
		variables.jsoup = javaloader.create("org.jsoup.Jsoup");
		return this;
	}
 
	function parse ( required string html ) {
		return variables.jsoup.parse( arguments.html );
	}
}

DI/1 built into FW/1 was intuitive once I had the naming convention down.

// controllers/main.cfc
component accessors = true {
	property fw;
    property markdownService;
    property postService;
	property pageService;
	property jsoupService;
 
    function default ( rc ) {
        rc['posts'] = postService.list();
		rc['tags'] = postService.getTags();
    }
 
	// ... more stuff
}

The main services done, the existing post and page services are ported to FW/1. Methods are created in main.cfc controller to support the application routes.

Static site generation is still missing, but it should be mostly copy/paste. Jasper's config structure needs to be created and the OG/Twitter metadata added. There is still much to be done, but the work will go quickly. Overall the application feels lean, but FW/1 is a compact framework. I look forward to playing around with it more.

Cover image by Aaron Burden on Unsplash

About Robert Zehnder
Robert is a Senior Lead ColdFusion Engineer. In his spare time he enjoys hanging out with his family, his dog, and working on cool stuff.