Hotwire Turbo Drive Doesn't Work With .cfm Page Extensions
Over the holiday break, I had this grand vision of building a ColdFusion site and then adding Hotwire (HTML Over The Wire) to it as a progressive enhancement. Unfortunately, it took me all of break just to get the ColdFusion parts written (I chose a poor problem space). And then, when I finally installed Hotwire and tried to use Turbo Drive, nothing happened. Every link and form submission lead to a full page refresh. After a few hours of Googling, I discovered that Hotwire Turbo Drive doesn't work with .cfm
file extensions.
According to GitHub Issue 519: Remove isHTML, this is by design. Hotwire Turbo Drive works by intercepting anchor links and form submissions, AJAX'ifying the interactions, and then preventing full-page reloads. However, it can only do this if the target of the link / form is known to be an HTML page. And, since dynamic server-side technologies like ColdFusion, PHP, Ruby, Python, .etc can serve up anything (such as image binaries), Hotwire cannot - in good conscience - assume that a .cfm
file extension will lead to HTML content.
The Basecamp team is considering how to open Hotwire up to more technologies by default - see GitHub issue above; but, in the meantime, in order to get Hotwire Turbo Drive working with my ColdFusion / Lucee CFML demo application, I switched all my .cfm
extensions to be .htm
and then enabled URL rewriting in my CommandBox server.
Thankfully, CommandBox this as easy as setting the BOX_SERVER_WEB_REWRITES_ENABLE
environment variable in my docker-compose.yml
file:
version: "2.4"
services:
lucee:
build:
context: "./docker/lucee/"
dockerfile: "Dockerfile"
ports:
- "80:8080"
- "8080:8080"
volumes:
- "./app:/app"
environment:
APP_DIR: "/app/wwwroot"
BOX_SERVER_APP_CFENGINE: "lucee@5.3.10+97"
BOX_SERVER_PROFILE: "development"
BOX_SERVER_WEB_REWRITES_ENABLE: "true" # <------- Enable URL rewrites!
cfconfig_adminPassword: "password"
HEALTHCHECK_URI: "http://lucee:8080/healthcheck.cfm"
LUCEE_CASCADE_TO_RESULTSET: "false"
LUCEE_LISTENER_TYPE: "modern"
LUCEE_PRESERVE_CASE: "true"
With this configuration in place, I was then able to go into my ColdFusion templates and changes all references of index.cfm
to be index.htm
. Here's my primary layout template, which demonstrates this change in the <nav>
element:
<!--- Reset the output buffer. --->
<cfcontent type="text/html; charset=utf-8" />
<cfoutput>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>
#encodeForHtml( request.template.title )#
</title>
<script src="/js/main.js" defer></script>
</head>
<body>
<div>
Sticky Tips
</div>
<nav>
<ul>
<li>
<a href="/index.htm?event=dashboard">Dashboard</a>
</li>
<li>
<a href="/index.htm?event=tip">Tips</a>
</li>
<li>
<a href="/index.htm?event=tippee">Tippees</a>
</li>
<li>
<a href="/index.htm?event=event">Events</a>
</li>
<li>
<a href="/index.htm?event=faq">FAQ</a>
</li>
</ul>
</nav>
<hr />
#request.template.body#
</body>
</html>
</cfoutput>
As you can see, all of my primary navigation link elements point to /index.htm
. This page doesn't actually exist. As such, the underlying J2E server is rewriting the request to be:
/index.cfm/index.htm
... where the originally requested resource (/index.htm
) becomes the cgi.path_info
value. My entire ColdFusion site routes through index.cfm
, so this URL rewrite is seamless - no extra work on my part.
Once I had the .htm
file extensions and this URL rewriting in place, I was able to install Hotwire Turbo:
npm install --save-dev @hotwired/turbo
And activate it with a simple import
:
// Import vendor modules.
import * as Turbo from "@hotwired/turbo";
// ----------------------------------------------------------------------------------- //
// ----------------------------------------------------------------------------------- //
document.addEventListener(
"turbo:load",
( event ) => {
console.log( "turbo:load event" );
}
);
document.addEventListener(
"turbo:before-visit",
( event ) => {
console.log( "turbo:before-visit event" );
}
);
document.addEventListener(
"turbo:before-fetch-request",
( event ) => {
console.log( "turbo:before-fetch-request event" );
}
);
document.addEventListener(
"turbo:before-render",
( event ) => {
console.log( "turbo:before-render event" );
}
);
Now, if I load the ColdFusion application and try clicking through the primary navigation links, we can see the Hotwire Turbo events being logged to the Chrome developer tools console. Note that the console log is persisting across clicks, indicating that the page is not refreshing, but is - instead - being progressively enhanced.
Finally, I can start exploring this whole Hotwire framework! I had intended to get back from holiday knowing all this stuff already; but, the sample ColdFusion app that I'm building is likely not a good fit for some of this stuff and is more complicated than it needed to be. Anyway, at least I can start to play now.
Want to use code from this post? Check out the license.
Reader Comments
I've been using hotwire a lot with Rails and it is awesome. I do love coldfusion though and thought I would search around and see if anyone has hooked hotwire up to it and found this! Great info. Any other work arounds other than command box to get around the cfm file extension? I have a CF 2018 prod server that is managed by a separate IT group.
@Chris,
URL rewriting would be the only workaround that I know of at this time. That said, in the GitHub issue, they talk about coming up with a way to do this more programmatically in the client-side code (ie, determine which files are Turbo'able). But, that's still in the discussion phase as I understand it.
I have a sense that all this Hotwire stuff is really cool; but, I'm struggling to figure out how to approach it. Part of me just wants to start adding Stimulus everywhere. But, I know that this is not the Hotwire way - that I should be pushing the boundaries of Turbo first, and then filling in the gaps with Stimulus. It's really turning my brain inside-out and I'm feeling rather stuck.
Stimulus is great too. I had done some basic tests integrating that into CF/Lucee. I need to spend some more time with it.
Looking into some things I see cbwire as well which sounds similar in concept.
https://cbwire.ortusbooks.com/
Have you worked with this at all?
@Chris,
I've tried a bit of Stimulus - it looks pretty nice, and keeps things with the Turbo Drive simple (in that it automatically connects / disconnects elements and controllers).
I have not tried CBWire yet - but, I was just watching a video demo of it over the weekend. It looks interesting, something I'll definitely take a closer look at - the Ortus people are very bright, so I have no doubt it's some great stuff.
As of Turbo 8, the file-extensions are still hard-coded into the Turbo library (ie, not exposed as a configuration option for the developer). I'm not yet ready to give up on using Hotwire in my ColdFusion projects. So, I've taken to forking the Turbo repository and updating the RegExp pattern matching to include
cfc|cfm|cfml
:www.bennadel.com/blog/4629-forking-hotwire-turbo-to-make-it-coldfusion-compatible.htm
All in all, not so bad! And, I learned some stuff about npm along the way.
Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →