<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>BarneyBlog &#187; tools</title>
	<atom:link href="http://www.barneyb.com/barneyblog/category/tools/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.barneyb.com/barneyblog</link>
	<description>Thoughts, rants, and even some code from the mind of Barney Boisvert.</description>
	<lastBuildDate>Mon, 02 Mar 2020 13:20:35 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>The Importance of Development Environments</title>
		<link>https://www.barneyb.com/barneyblog/2011/12/20/the-importance-of-development-environments/</link>
		<comments>https://www.barneyb.com/barneyblog/2011/12/20/the-importance-of-development-environments/#comments</comments>
		<pubDate>Wed, 21 Dec 2011 05:59:37 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[development]]></category>
		<category><![CDATA[tools]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1749</guid>
		<description><![CDATA[My first job as a software developer was in 1998, while I was an undergrad at the University of Arizona in Tucson, AZ.Â  The project was simple: build a JavaScript application to front a website that helped people incorporate a new business.Â  This was long before "ajax" became the new hotness, but I'd built full [...]]]></description>
			<content:encoded><![CDATA[<p>My first job as a software developer was in 1998, while I was an undergrad at the University of Arizona in Tucson, AZ.Â  The project was simple: build a JavaScript application to front a website that helped people incorporate a new business.Â  This was long before "ajax" became the new hotness, but I'd built full windowing environments in JS (supporting both NS4 and IE4) back in the mid '90s so this was old hat for me.Â  The client had a single environment.Â  Production.</p>
<p>This was not ideal.</p>
<p>My next job was after I'd dropped out of school, up in Portland, OR.Â  It was a small web development agency and we had a dedicated development server in our office which we worked on, and then separate production servers colocated offsite.Â  This was much better, but still meant we (the developers) stepped on each others toes all the time.</p>
<p>Getting better&#8230;</p>
<p>Next came working for a university on some internal project, still in Portland, OR.Â  I was the sole coder, and while I had my full development environment on my local machine, the most striking difference wasn't that it was local (and therefore fast and easy to screw with), but that I didn't have to compete with other devs' changes.Â  This was a big boon, and my productivity demonstrated it.Â  But still no version control, if you can believe it.Â  I still don't understand why that isn't forced on students in CS 101 before the first code project.</p>
<p>Almost there&#8230;</p>
<p>My first "real" job was up in Bellingham, WA with a company which sold communications management software.Â  The whole app was ColdFusion and sold with a SaaS model (about the same time as Salesforce.com was conceived).Â  There we not only had a production cluster, we had a staging environment, a dev (bleeding edge) environment, and per-developer environments on our local workstations.Â  It was here that I learned about version control, saw that it was a Good Thing<sup>(tm)</sup>, and put on my tantrum face and forced everything to halt until we had it implemented.Â  We used CVS, and then upgrade to SVN just after it went 1.0 (after watching it get closer and closer with bated breath).</p>
<p>We've got it!</p>
<p>My next job was back in good old Portland, OR.Â  I had to create SVN infrastructure, but no worries.Â  I knew what we needed, especially with six developers.Â  Also had to create local-workstation development environments, but again a completely worthwhile investment.Â  The trick was learning to leverage all of this, which brings me to the point.</p>
<p style="padding-left: 30px;">Every developer should be able to blindly and without regard for any consequences thrash their working codebase to see if random idea X works.</p>
<p>It has taken me a decade to really appreciate how important this simple idea is.</p>
<p>If you can't do this with your current development environment, you should invest some time in making it possible.Â Â  It means you <em>need</em> to be able to break any and every piece of code without affecting anyone else.Â  Including yourself if you need to fix some urgent bug all of a sudden.Â  It means you should be able to use version control if your idea takes 75 commits to test out, without affecting anyone else.Â  Including yourself as before.Â  It means that if your idea works, you shouldn't have to struggle to share your solution and get it into the next nightly.Â  Even if you're the only developer.</p>
<p>So what do you need?Â  You need (at least):</p>
<ol>
<li>version control which supports concurrent editing, branching and merging</li>
<li>a completely isolated instance of your software project with it's own working directory</li>
<li>the ability to create <em>n</em> copies of #2</li>
<li>a habit of small atomic commits (commit early, commit often)</li>
<li>the confidence in your setup to trust that no matter how bad you screw things up, you can always get back to any previous state whether it was five minutes or five months ago</li>
</ol>
<p>The first points is a no brainer, but is vitally important as it is the only way to get the remaining four items.Â  Points two and three are different sides of the same thing as #3 is really just #2 where you have two personalities (normal dev and crazy hacker dude).</p>
<p>Point four is a learned skill.Â  This isn't a piece of technology you adopt or something you do, it's a habit you consciously work to build because you want to be a better developer.Â  It's hard.Â  Everyone knows it's far easier to just take a pile of changes you've made and commit them with a message like "fixed stuff".Â  Suppress that urge.Â  Don't give in to the easy road.Â  Spend the time to fix one thing, ensure it works, ensure it's atomic, and commit it.Â  Then move on to the next thing.Â  If you're working on eight things at once, first go talk to your boss because they're clearly insane, and then think about point three and do each task in a separate working copy (perhaps even against a separate branch).Â  This might seem like pure hassle and no benefit, but it's the only way to make the investment you've made with points one to three actually pay off.Â  Version control repositories with non-atomic commits are certainly better than no version control at all, but the non-atomicity severely hamstrings their primary objective: the ability to act as a time machine.</p>
<p>Which brings me to the final point.Â  Confidence is a learned skill.Â  It comes partially from trying and succeeding, but it mostly comes from trying and failing and learning that your precautions keep you safe.Â  It's important to pick up random idea X and give it a whirl.Â  Really important.Â  And the only way to go about it is with the confidence you can recover from <em>anything</em>.Â  Anything.Â  If you don't have that confidence, you're going to go about it in a half-ass manner in case something unexpected comes up.Â  It's human nature; don't feel embarrassed.Â  What you have to realize is that because we are software developers, we have a distinct advantage.Â  We have a time machine.Â  We can go back in time, to any point in time, and start over fresh.Â  The lack of physical deliverables gives us this advantage, and we'd be fools to not take full advantage of it.</p>
<p>If you can't do that today, I strongly encourage you to think about ways you can get closer.Â  Don't worry about trying to get there in one step, just get closer.Â  Do that repeatedly and you'll get there, and every step closer has tangible benefit to future development.Â  It is worth every ounce of energy it takes to get there.Â  It's also important to look at new projects with the same mindset; setting yourself (and your fellow developers) up for this from the get-go is simple if you have the forethought to do it.</p>
<p>I have confidence in my development environment.Â  It's not perfect, but I have no qualms about ripping the heart out of a 300,000 line subsystem and seeing if I can put it back together.Â  That confidence means I can act.Â  I don't have to worry about consequences until I can reasonably weigh their ROI.Â  No more "what ifs".Â  Interestingly, every Star Wars nerd's favorite line is exactly wrong when applied to software: <span style="text-decoration: line-through;">"do or do not, there is no try"</span> "try, there is no do or do not".</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2011/12/20/the-importance-of-development-environments/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Adobe, Mobile Flash Player, JavaScript, etc.</title>
		<link>https://www.barneyb.com/barneyblog/2011/11/10/adobe-mobile-flash-player-javascript-etc/</link>
		<comments>https://www.barneyb.com/barneyblog/2011/11/10/adobe-mobile-flash-player-javascript-etc/#comments</comments>
		<pubDate>Thu, 10 Nov 2011 18:28:55 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[ajax]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[flex]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[tools]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1743</guid>
		<description><![CDATA[Before you click over this because you know me as a Flash hater, give me two seconds.Â  That's not what this post is about.Â  It's about a larger issue.Â  It's about how awesome it is to be a web developer these days.
Ten years ago, being a web developer sucked.Â  Deployment was easy (rsync to production [...]]]></description>
			<content:encoded><![CDATA[<p>Before you click over this because you know me as a Flash hater, give me two seconds.Â  That's not what this post is about.Â  It's about a larger issue.Â  It's about how awesome it is to be a web developer these days.</p>
<p>Ten years ago, being a web developer sucked.Â  Deployment was easy (rsync to production and done), but the tooling available to us was dismal.Â  And I mean crying-naked-in-a-snowstorm dismal.Â  Browsers were inconsistent, their built-in programming environment (read JavaScript) was reasonably functional but horridly slow, and hardware wasn't beefy enough to deal with scripting languages for hard-core number crunching.</p>
<p>But we had the Flash Player.Â  Flash provided an environment that beat all three of those problems, and beat them soundly.Â  It was consistent across browsers and operating systems, used a similar language to what we were used to (both JavaScript and ActionScript are ECMAScript implementations), and it let the developer compile the script into something a little lower level to run in a dedicated VM on the user's machine which meant it was faster.Â  Of course, Flash is an animation toolkit, but we figured out how to bastardize it with the single-frame movie with a include-and-stop script in it so we could build applications entirely with script (like we wanted), but leverage the Flash player to actually run them.Â  Not to mention the rich support for visual stuff.Â  All this led to the concept of a RIA (Rich Internet Application).Â  Something similar to what we had on the desktop, but deployed to the web with all the benefits (and some drawbacks) that has.</p>
<p>Then browsers got their act together.Â  We started seeing a unifying focus on application development with the browser as the environment.Â  People got serious about fast JavaScript runtimes.Â  Standards were written (e.g., CSS2/3, Canvas) and largely adhered to.Â  And hardware got faster.Â  JavaScript application frameworks (EXT, YUI, GWT, etc.) showed up to leverage all that, and now it was possible to build RIAs using standards supported by a wide array of vendors.</p>
<p>In order to compete with this, Adobe released Flex, which is an application development framework for deploying to Flash Player.Â  It was horridly expensive, difficult to work with, had all kinds of implementation problems, but was better than what you got with JavaScript.Â  For a while.Â  Unfortunately a single software company, even one of the largest in the world, couldn't hope to compete with the widespread interest and momentum around browser-native RIA development.Â  Flex died as a web application framework pretty much before it was released.Â  Which isn't to say it wasn't used (it was and still is), but the browser RIA juggernaut crushed it like a bug.</p>
<p>Then we saw the huge surge in mobile devices.Â  It started with smartphones and now includes tablets and e-readers of various form factors.Â  Fortunately for both consumers and manufacturers, the web wasn't new, so they were able to jump right on top of all the standards and browser capabilities which had been created for the desktop.Â  A huge market segment opened up for web application developers and browser-native was reasonable right out of the box.</p>
<p>Adobe again tried to compete by creating Mobile Flash Player, but the benefits of Flash Player are small on new mobile devices, especially considering that so much of a mobile device experience is through web-connected native applications, not traditional web applications.Â  And here we have Adobe's smartest move yet around Flash Player: killing it for the mobile market in favor of restructuring the ecosystem around using it as a development environment for native applications.</p>
<p>Unfortunately, I don't think it's going to matter because Flash is still really slow and heavy and it's not really that much better to develop with than the truly native dev kits.Â  Yes, it offers the promise of cross-platform deploy, but just as Java demonstrated 10-15 years ago, that isn't much of a selling point for a single application.Â  People expect not just native execution, they expect native idioms, which means you have to develop for a specific platform, even if you're using a cross-platform toolkit.</p>
<p>So where does that leave us?Â  As consumers it leaves us in a great spot: there are lots of ways developers can deliver engaging applications to us, on all our devices.Â  As developers it leaves us in a weird spot: we're stuck with being either web or native developers, with Flash now trying to occupy a sort of middle ground (develop web-style, deploy native).Â  On the desktop I think Flash (via AIR) holds some promise, but ultimately I don't think it'll last.Â  The platform just isn't compelling enough to justify the dedication it requires to use it.Â  Java was designed for very much the same purpose as AIR, and virtually no one uses it.Â  Java moved almost entirely server side, something which Flash isn't likely to do.</p>
<p>Most interesting is the PhoneGap/Titanium/etc. movement which is very much paralleling the browser resurgence I talked about earlier.Â  Huge communities of people are working to take all the skills we have as web application developers and give us a build process to take web-ish apps and compile them into native applications, in much the same way Adobe has use AIR to compile Flash into native apps.Â  However, I think Flash is going to lose in exactly the same way, and for exactly the same reasons, as it lost in the browser.</p>
<p>Bottom line, if you use the web or a web-connected device (read: everybody) the world is going to be glorious in a couple years and only get better.Â  If you're a developer trying to work in that space, you need to learn browser technologies.Â  It's the way of the future.Â  Flash had it's run, but it's been on the way out for a long while.Â  It'll stick around, just like COBOL and Fortran have, but without an alternative path like Java ended up taking it isn't going to stay relevant to mainstream developers.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2011/11/10/adobe-mobile-flash-player-javascript-etc/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>JIRA Subtask Manager</title>
		<link>https://www.barneyb.com/barneyblog/2011/09/15/jira-subtask-manager/</link>
		<comments>https://www.barneyb.com/barneyblog/2011/09/15/jira-subtask-manager/#comments</comments>
		<pubDate>Thu, 15 Sep 2011 18:28:43 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[tools]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1724</guid>
		<description><![CDATA[If you use subtasks in JIRA, you've likely had issues with trying to manage them.Â  It's very difficult to get a comprehensive view of subtasks for a ticket all in one screen.Â  Using a little tweak to the JIRA configuration and a small Greasemonkey script, I think I've made the process much easier.
First, you need [...]]]></description>
			<content:encoded><![CDATA[<p>If you use subtasks in JIRA, you've likely had issues with trying to manage them.Â  It's very difficult to get a comprehensive view of subtasks for a ticket all in one screen.Â  Using a little tweak to the JIRA configuration and a small <a href="https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/">Greasemonkey</a> script, I think I've made the process much easier.</p>
<p>First, you need to enable the 'description' field in the subtask list.Â  Open up your '<code>/WEB-INF/classes/jira-application.properties</code>' file and find the '<code>jira.table.cols.subtasks</code>' property.Â  Add 'description' to it (I put it after 'summary').Â  Similarly, find the '<code>jira.subtask.quickcreateform.fields</code>' property and add 'description' to it also.Â  Then save and restart JIRA.</p>
<p>Second, load up <a href="http://www.barneyb.com/s/jira_subtask_manager_improvements.js">this userscript</a> (make sure to fix the server to match your JIRA installation) into Greasemonkey.Â  It does a few things, but primarily it moves the description into its own row and changes the summary to link to the edit form instead of the browse page.</p>
<p>Also note that if you want to reorder the subtasks more efficiently, you can very easily tweak the URLs for the up/down links to move more than one step at a time.Â  If you copy the URL of one of the links you'll see two numbers in it.Â  The first number is the current position of the issue and the second is the desired position.Â  They'll always be one apart, but you can change the second number to any valid position to jump the subtask to that position in the list.Â  Hardly an ideal interface, but it's faster than moving one step at a time if you have to move more than a couple spots.Â  Eventually I'll probably improve that in the userscript to some level, but not exactly sure how I want it to work so I'm leaving it as-is for now.</p>
<p>Update 2011-09-16: I've made a couple additional tweaks to the script and removed the inline version.Â  Completed subtasks are now greyed out and descriptions hidden so they're not as intrusive.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2011/09/15/jira-subtask-manager/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Outlook Web App Title Bar</title>
		<link>https://www.barneyb.com/barneyblog/2011/05/02/outlook-web-apptitle-bar/</link>
		<comments>https://www.barneyb.com/barneyblog/2011/05/02/outlook-web-apptitle-bar/#comments</comments>
		<pubDate>Mon, 02 May 2011 22:31:22 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[tools]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1653</guid>
		<description><![CDATA[If you've used the Outlook Web App in the past, you were likely unimpressed.Â  I was.Â  Until the latest version (number 14), that is.Â  It's not GMail, but it's absolutely above the "usable" threshold, which is awesome.Â  And I might add that I'm using it in Firefox on Linux &#8211; not Microsoft's favorite platform.Â  However, [...]]]></description>
			<content:encoded><![CDATA[<p>If you've used the Outlook Web App in the past, you were likely unimpressed.Â  I was.Â  Until the latest version (number 14), that is.Â  It's not GMail, but it's absolutely above the "usable" threshold, which is awesome.Â  And I might add that I'm using it in Firefox on Linux &#8211; not Microsoft's favorite platform.Â  However, it does have one major usability issue: you can't see how many unread messages you have in your inbox without opening the window.Â  But fortunately, through the magic of <a href="https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/">GreaseMonkey</a>, that's easy to fix:</p>
<pre>// ==UserScript==
// @name           Outlook Web App More Gooderer
// @namespace      owa.barneyb.com
// @include        https://*/owa/
// @include        http://*/owa/
// ==/UserScript==

(function() {
  var originalTitle = unsafeWindow.document.title
  var f = function() {
    var ss = unsafeWindow.document.getElementsByTagName('span')
    for (var i = 0, l = ss.length; i &lt; l; i++) {
      if (ss[i].getAttribute('fldrnm') == 'Inbox') {ï»¿ï»¿ // 'Unread Mail' is also reasonable
        var children = ss[i].parentNode.childNodes
        var count = 0
        if (children.length == 2) {
          count = children[1].childNodes[1].innerHTML
        }
        unsafeWindow.document.title = 'Outlook ' + (count == 0 ? '' : '(' + count + ')')
          + ' - ' + originalTitle.split('-')[0]
        break
      }
    }
  }
  setInterval(f, 15 * 1000)
  f()
})()
</pre>
<p>Just drop that into a new user script, ensure the @include directives will match your OWA endpoint, and reload.Â  Now when the app loads it'll tweak your titlebar to put the useful info (unread message count) in there over to the left and move the useless information (your own name) over to the right.Â  Much better.Â  It'll also redo the same check every 15 seconds, so your title bar stays up to date while the window is minimized.Â  YAY!</p>
<p>Is this the most elegant solution to this problem?Â  No. Is it even an elegant solution?Â  Probably not.Â  Does it work and remain unintrusive?Â  Absolutely.Â  If you want to help with the first consideration, drop me a line.Â  In particular, some sort of event-driven approach would be far better so you don't have to wait for the 15 seconds to elapse before changes are reflected in the title bar.Â  If you're like me, you probably don't care enough to dig into it to that level, but if you have the time/knowledge/desire, please drop me a line.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2011/05/02/outlook-web-apptitle-bar/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>On-The-Fly YUI Compressor</title>
		<link>https://www.barneyb.com/barneyblog/2010/08/03/on-the-fly-yui-compressor/</link>
		<comments>https://www.barneyb.com/barneyblog/2010/08/03/on-the-fly-yui-compressor/#comments</comments>
		<pubDate>Wed, 04 Aug 2010 01:35:14 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[cfml]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[tools]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1570</guid>
		<description><![CDATA[A couple years ago I wrote about using YUI Compressor to do built-time aggregation and compression of static assets.Â  That works well and good if you have a build environment, but that's not always the case.Â  So, still using YUI Compressor, I set up a simple script that'll do runtime aggregation and compression of assets [...]]]></description>
			<content:encoded><![CDATA[<p>A couple years ago <a href="http://www.barneyb.com/barneyblog/2008/04/08/build-time-aggregation-of-jscss-assets/">I wrote about</a> using <a href="http://developer.yahoo.com/yui/compressor/">YUI Compressor</a> to do built-time aggregation and compression of static assets.Â  That works well and good if you have a build environment, but that's not always the case.Â  So, still using YUI Compressor, I set up a simple script that'll do runtime aggregation and compression of assets using my favorite mod_rewrite-based file caching mechanism.</p>
<p>The basic idea is that your HTML includes a reference to "agg_standard.js", which is an alias for whatever JS files you need for your standard user (as opposed to a mobile user, for example).Â  That request comes to your server and if the file exists, gets served back like any other JS file.Â  If it <em>doesn't</em> exist, however, mod_rewrite will forward it to a CFM page to generate it:</p>
<pre>RewriteCondÂ Â Â Â  %{REQUEST_FILENAME}Â Â Â Â  !-s
RewriteRuleÂ Â Â Â  (/my_app)/static/(agg_.*)\.js$ $1/aggregator.cfm?name=$2.js
</pre>
<p>In our example, the request passed to aggregator.cfm would have "agg_standard.js" as the 'name' attribute, which is how we'll figure out what we need to aggregate together:</p>
<pre>switch (url.name) {
case "<span style="color: #0000ff;">agg_standard.js</span>":
  files = [
    "jquery/jquery" &amp; (request.isProduction ? ".min" : ""),
    "jquery/jquery.ui" &amp; (request.isProduction ? ".min" : ""),
    "yui/yahoo-min",
    "yui/event-min",
    "yui/history-min",
    "util",
    "jscalendar/calendar",
    "jscalendar/lang/calendar-en",
    "jscalendar/calendar-setup",
    "dracula/raphael-min",
    "dracula/graffle",
    "dracula/graph"
  ];
  break;
case "agg_iphone.js":
  files = [
    "jquery/jquery" &amp; (request.isProduction ? ".min" : ""),
    "yui/yahoo-min",
    "yui/event-min",
    "yui/history-min",
    "util"
  ];
  break;
}
</pre>
<p>The important bits, of course, is the Groovy script that actually does the aggregation and compression.Â  It uses YUI Compressor, so you'll need to have the yiucompressor-x.y.z.jar file on your classpath (probably in /WEB-INF/lib).Â  Here it is:</p>
<pre>import com.yahoo.platform.yui.compressor.*

sw = new StringWriter()
<span style="color: #0000ff;">variables.files</span>.each {
  def f = new File(variables.STATIC_DIR + it + '.js')
  sw.append('/* ').append(f.name).append(' */\n')
  if (! it.endsWith(".min") &amp;&amp; ! it.endsWith("-min")) {
    def compressor = new JavaScriptCompressor(f.newReader(), null)
    compressor.compress(sw, -1, false, false, false, false)
  } else {
    sw.append(f.text.trim())
  }
  sw.append('\n')
}
variables.buffer = sw.toString()</pre>
<p>Pretty straightforward: it just loops over the files, using a StringWriter to build up the aggregated buffer.Â  Each file is either compressed into the Writer or simply appended based on whether the file has already been minified (based on ".min" or "-min" in the filename).Â  Each file also gets a comment label in the Writer above it's contents so that the aggregated file is a little easier to parse (at the expense of a few extra bytes).Â  Once done, the Writer's contents are stored in the 'buffer' variable caching on the filesystem:</p>
<pre>&lt;cfset fileWrite(STATIC_DIR &amp; url.name, buffer) /&gt;
&lt;cflocation url="#url.name#" addtoken="false" /&gt;</pre>
<p>You'll notice that I'm not streaming the buffer back out to the user, but instead 302-ing back to the same URL.Â  This is important.Â  The reason is that Apache does a whole bunch of stuff to optimize static assets, and if I serve the content back with CFCONTENT, I'll miss out on all of that.Â  Yes, the 302 has a little bit of overhead on the initial page load, but it reduces the total transfer size by several hundred KB (because of the GZIPping), and avoids a rerequest on the next page load (because of the cache headers).Â  So it's completely worth it, the moreso because this is an application likely to generate extended usage rather than a content-centric site that is likely to see single-page "bounce" visits from search engines.</p>
<p>The last piece of the puzzle is handling versioning of your assets.Â  When you change your JS file, you necessarily have to invalidate your cache (by deleting the files) so the aggregated version can be rebuilt with the new JS.Â  The easiest way to do that is to use psuedo-versioning of your assets.Â  You'll see a lot of sites will add a timestamp or a version number to their files (e.g., "arc/yui/reset_2.6.5.css" from Yahoo.com) so that when the update the file it gets a new filename, and is therefore redownloaded by everyone (because it doesn't exist in their cache).Â  That's great, but it means you have to rename your files all the time which is kind of a pain.Â  But you can fake it:</p>
<pre>&lt;cfset STATIC_ASSET_VERSION = 15 /&gt;
&lt;script type="text/javascript" src="static/agg_standard_#STATIC_ASSET_VERSION#.js"&gt;&lt;/script&gt;
</pre>
<p>That'll generate a request to "agg_standard_15.js", as you might imagine, which isn't going to work so well.Â  But we can just change the 'switch' line from the first snippet to this:</p>
<pre>switch (REReplace(url.name, "^(agg_.*?)(_[0-9]+)?\.js$", "\1.js")) {</pre>
<p>Now it'll strip out that "fake" number string and switch on just "agg_standard.js", which is what we want.Â  But that 'fileWrite' call later will still use the full filename (with the number embedded).Â  That way subsequents will get the filesystem cacheing, the headers and GZIPping from Apache, and all the other love.Â  And when you rev your files, you need only increment the STATIC_ASSET_VERSION variable and you'll have a brand new set of virtual URLs for all your assets, no fuss, no muss.</p>
<p>Oh, and just in case you're wondering, the aggregation and compression is <em>fast</em>.Â  If you've ever used the command line or Ant task, you might fear that it's slow, but most of the time you see there is from the JVM spinning up, not the actual compression.Â  Since this is all in-JVM, you don't pay any of that cost.Â  It's certainly not fast enough to have it run in production on every request (hence the file caching), but it's totally reasonable to do on your production box as part of deploying a new version of the app.Â  It's also probably fast enough to have running every request on your internal test/staging boxes, though that'll depend on how much you're aggregating/compressing among other things.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2010/08/03/on-the-fly-yui-compressor/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CF9 Compatibility for FB3Lite</title>
		<link>https://www.barneyb.com/barneyblog/2010/03/30/cf9-compatibility-for-fb3lite/</link>
		<comments>https://www.barneyb.com/barneyblog/2010/03/30/cf9-compatibility-for-fb3lite/#comments</comments>
		<pubDate>Tue, 30 Mar 2010 22:26:57 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[coldfusion]]></category>
		<category><![CDATA[fusebox]]></category>
		<category><![CDATA[tools]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1407</guid>
		<description><![CDATA[I just made another minor tweak to FB3Lite to fix out-of-the-box ColdFusion 9 compatibility.Â  CF9 added a 'location' built-in function, which means that the function of the same name that FB3Lite provides now generates a compiler error.Â  Fortunately, since functions are real data within CFML, a simple realiasing gets around the issue.Â  This creates a [...]]]></description>
			<content:encoded><![CDATA[<p>I just made another minor tweak to <a href="http://www.barneyb.com/barneyblog/projects/fb3lite/">FB3Lite</a> to fix out-of-the-box ColdFusion 9 compatibility.Â  CF9 added a 'location' built-in function, which means that the function of the same name that FB3Lite provides now generates a compiler error.Â  Fortunately, since functions are real data within CFML, a simple realiasing gets around the issue.Â  This creates a subtle compatibility issue, but better than having to delete the UDF manually.</p>
<p>If your CFML engine does not provide a location function, FB3Lite will ensure one is available for you (simply wrapping CFLOCATION).Â  This has been the behaviour since the beginning.Â  Now, if your CFML engine <strong>does</strong> provide a location function, FB3Lite will still provide one in the variables scope (in case you're doing dynamic evaluation of it), but the engine-provided one will be used for static references (the typical case).</p>
<p>In general, this shouldn't matter to anyone; it's still just download and go.Â  The issue I alluded to above has to do with the optional parameters that CF9 provides (addToken and statusCode).Â  Since static invocations will use the built-in function, those optional params <strong>will</strong> be available on CF9, but <strong>will not</strong> on platforms that rely on FB3Lite's version.Â  Eventually I'd expect all CFML engines to provide a location function in which case there will no longer be a discrepancy, but for the meantime, if you're using multiple platforms, you'll need to ensure you're not using the optional parameters with the location function.</p>
<p>And before you say it, adding the parameters to the FB3Lite-provided function isn't an option, because CFLOCATION doesn't support statusCode until ColdFusion 8, which means you'd get compiler errors on CF7.</p>
<p>As always, the <a href="https://ssl.barneyb.com/svn/barneyb/fb3lite/trunk/index.cfm">source is available</a>, or you can check the <a href="http://www.barneyb.com/barneyblog/projects/fb3lite/">project page</a> for the latest info and updates.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2010/03/30/cf9-compatibility-for-fb3lite/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>FB3Lite appSearchPath Supports Mappings</title>
		<link>https://www.barneyb.com/barneyblog/2010/03/30/fb3lite-appsearchpath-supports-mappings/</link>
		<comments>https://www.barneyb.com/barneyblog/2010/03/30/fb3lite-appsearchpath-supports-mappings/#comments</comments>
		<pubDate>Tue, 30 Mar 2010 21:41:30 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[fusebox]]></category>
		<category><![CDATA[tools]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1402</guid>
		<description><![CDATA[Piggybacking on the change to allow mappings in do/include, you can now use mapping-relative paths in the appSearchPath initialization variable as well.Â  Before you had to use a relative path, which got a little hairy when you had a deeply nested structure:
&#60;cfset appSearchPath = "../../../myApp" /&#62;
&#60;cfinclude template="../com/barneyb/fb3lite/index.cfm" /&#62;

But now with mappings, you can simplify things, [...]]]></description>
			<content:encoded><![CDATA[<p>Piggybacking on the <a href="http://www.barneyb.com/barneyblog/2010/03/16/minor-fb3lite-feature/">change to allow mappings in do/include</a>, you can now use mapping-relative paths in the appSearchPath initialization variable as well.Â  Before you had to use a relative path, which got a little hairy when you had a deeply nested structure:</p>
<pre>&lt;cfset appSearchPath = "../../../myApp" /&gt;
&lt;cfinclude template="../com/barneyb/fb3lite/index.cfm" /&gt;
</pre>
<p>But now with mappings, you can simplify things, even if you're just using the webroot:</p>
<pre>&lt;cfset appSearchPath = "/myApp" /&gt;
&lt;cfinclude template="/com/barneyb/fb3lite/index.cfm" /&gt;
</pre>
<p>This is exactly the same functionality I added to do/include.Â  The difference is that the top-level fuseaction is invoked via an internal call to do() which is based on appSearchPath instead of a circuit prefix.Â  But now both are equivalent again.</p>
<p>As always, <a href="https://ssl.barneyb.com/svn/barneyb/fb3lite/trunk/index.cfm">source is available</a>, along with the <a href="http://www.barneyb.com/barneyblog/projects/fb3lite/">project page</a> with current information and downloads.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2010/03/30/fb3lite-appsearchpath-supports-mappings/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Minor FB3Lite Feature</title>
		<link>https://www.barneyb.com/barneyblog/2010/03/16/minor-fb3lite-feature/</link>
		<comments>https://www.barneyb.com/barneyblog/2010/03/16/minor-fb3lite-feature/#comments</comments>
		<pubDate>Tue, 16 Mar 2010 21:58:32 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[cfml]]></category>
		<category><![CDATA[tools]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1387</guid>
		<description><![CDATA[If you've used FB3Lite, you may or may not know that it implicitly supports a Fusebox-like circuit structure.Â  Both do() and include() allow you to invoke fuseactions/templates from other directories within your application, though without the circuit aliasing abstraction layer that Fusebox provides.Â  This is a great way to break down large applications into multiple [...]]]></description>
			<content:encoded><![CDATA[<p>If you've used <a href="http://www.barneyb.com/barneyblog/projects/fb3lite/">FB3Lite</a>, you may or may not know that it implicitly supports a Fusebox-like circuit structure.Â  Both do() and include() allow you to invoke fuseactions/templates from other directories within your application, though without the circuit aliasing abstraction layer that Fusebox provides.Â  This is a great way to break down large applications into multiple sub sections, or relate multiple separate applications into a single URL space (especially combined with URL rewriting).</p>
<p>The new feature is the ability to dereference directories with webroot/mapping relative paths (e.g., /mapping/dir/file.cfm).Â  Until now, only relative paths (to the current circuit) were allowed, as they were always prefixed with the current circuit's path.Â  But now if you start a do/include path with a slash, it'll be assumed a webroot/mapping relative path and be used as-is.</p>
<p>As you'd expect, there is a corresponding 'allowMappedCircuits' settings variable that can be used to disable this behaviour if your application requires the old-style behaviour (of leading slashes being ignored).</p>
<p>The newest version is <a href="https://ssl.barneyb.com/svn/barneyb/fb3lite/trunk/index.cfm">available here</a>, or look to the <a href="http://www.barneyb.com/barneyblog/projects/fb3lite/">project page</a> for links to the demo app, a history of revisions, and any new info that might come up.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2010/03/16/minor-fb3lite-feature/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>WordPress Shortcodes in CFML</title>
		<link>https://www.barneyb.com/barneyblog/2010/03/13/wordpress-shortcodes-in-cfml/</link>
		<comments>https://www.barneyb.com/barneyblog/2010/03/13/wordpress-shortcodes-in-cfml/#comments</comments>
		<pubDate>Sat, 13 Mar 2010 18:27:53 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[cfml]]></category>
		<category><![CDATA[tools]]></category>
		<category><![CDATA[wordpress]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1375</guid>
		<description><![CDATA[If you've used WordPress, you may have run across shortcodes.Â  They're little directives you type into the editor box which then evaluate dynamically when the content is rendered on the outside of you site.Â  Plugins can register new shortcode handlers, which are implemented as a simple function callback.Â  It's a really simple way to expose [...]]]></description>
			<content:encoded><![CDATA[<p>If you've used WordPress, you may have run across shortcodes.Â  They're little directives you type into the editor box which then evaluate dynamically when the content is rendered on the outside of you site.Â  Plugins can register new shortcode handlers, which are implemented as a simple function callback.Â  It's a really simple way to expose psuedo-programming constructs to content authors in a safe manner (because you as the admin/developer can control which shortcodes are available), and without requiring any PHP knowledge and/or server access.</p>
<p>I needed this sort of functionality in CFML, so after playing with a few different syntaxes and parsers for them, I decided that a direct port of the WordPress shortcodes implementation was the best choice.Â  The code is pretty small (the grammar is context free and the parser is RegEx-based), and the port (including unit tests) took perhaps an hour and a half.Â  I had to roll my own <a href="http://www.barneyb.com/barneyblog/2010/03/10/rereplacecallback-udf/">REReplaceCallback UDF</a> to match one of the PHP builtins, as well as change the callback API slightly to deal with CFML idoms, but it's a pretty direct port.</p>
<p>So what can you do with shortcodes?Â  Here's a <a href="http://www.barneyb.com/r/shortcodes/">little demo</a>, both of the front side (the content) and the backside (the handlers and processing).Â  There is also a link to <a href="https://ssl.barneyb.com/svn/barneyb/shortcodes/trunk">the source</a>, of course.Â  And like all my projects, there is a <a href="http://www.barneyb.com/barneyblog/shortcodes/">project page</a> where current project information will always be available.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2010/03/13/wordpress-shortcodes-in-cfml/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Fake Filenames for Far Future Expires Headers</title>
		<link>https://www.barneyb.com/barneyblog/2010/03/10/fake-filenames-for-far-future-expires-headers/</link>
		<comments>https://www.barneyb.com/barneyblog/2010/03/10/fake-filenames-for-far-future-expires-headers/#comments</comments>
		<pubDate>Thu, 11 Mar 2010 02:45:05 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[tools]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1362</guid>
		<description><![CDATA[Everyone knows that one of the best ways to increase page performance is to reduce the number of HTTP requests required for related assets.Â  There are a pile of ways to approach this (JS/CSS aggregation, image sprites, caching), but the best (and simplest) is client-side caching.Â  It doesn't help your first-time visitors at all (which [...]]]></description>
			<content:encoded><![CDATA[<p>Everyone knows that one of the best ways to increase page performance is to reduce the number of HTTP requests required for related assets.Â  There are a pile of ways to approach this (JS/CSS aggregation, image sprites, caching), but the best (and simplest) is client-side caching.Â  It doesn't help your first-time visitors at all (which is why you still need to look at other optimizations), but for repeat visitors and people who stick around for more than one page caching is a huge win.</p>
<p>The key to all of this is the Expires header: you want to set an expiration <em><strong>way</strong></em> in future so the client (the browser) will cache the asset forever.Â  With Apache it's really simple:</p>
<pre>LoadModule expires_module modules/mod_expires.so
ExpiresByTypeÂ Â  application/x-javascriptÂ Â Â  "now plus 10 year"
</pre>
<p>That tells Apache to set an expiration on all JS files that is 10 years in the future.Â  In web time, that's pretty much forever.Â  Really simple, really effective.Â  You also want to use mod_headers to set Cache-Control and Pragma headers and remove the ETag and Last-Modified headers, as well as ensure we don't use file ETags:</p>
<pre>LoadModule headers_module modules/mod_headers.so
HeaderÂ  setÂ Â Â Â  Cache-ControlÂ Â Â  "public"
HeaderÂ  setÂ Â Â Â  PragmaÂ Â Â Â Â Â Â Â Â Â  ""
HeaderÂ  unsetÂ Â  Last-Modified
HeaderÂ  unsetÂ Â  ETag
FileETagÂ Â Â Â Â Â Â  None
</pre>
<p>The problem is that if you ever change one of those JS files, clients who already have the old version will never know about it.Â  The solution is to never modify files, only create and delete files.Â  So if you have 'script.js' and you need to make an update, instead of pushing a new version of that file, you'd instead push 'script_2.js' (or whatever).Â  That way you're guaranteed that every client will download it afresh (with the long Expires header) because no one has ever seen the file before.Â  Next time you need to make changes, you'd push 'script_3.js'.</p>
<p>This quickly becomes a bit of a problem, because not only do you have to change the filename, you have to change all the <em>references</em> to the filename as well.Â  So a little JS tweak suddenly becomes a change to all your SCRIPT tags and republishing all your content.Â  Not too fun.Â  This is the problem I can help solve, and it'll be using our friend mod_rewrite (of course!).</p>
<p>Check this innocuous little condition/rule:</p>
<pre>RewriteCondÂ Â Â  %{REQUEST_FILENAME}Â Â Â  !-s
RewriteRuleÂ Â Â  (.*)_[0-9]+\.(js|css)$Â Â Â  $1.$2
</pre>
<p>That says any time you find a JS file that ends with an underscore followed by one or more digits, if it doesn't exist, remove the underscore and digits.Â  I.e. when 'script_2.js' gets requested, if that file doesn't exist, just serve back 'script.js' instead.Â  Now that's handy, because now we can use an arbitrary version number and they all hit the same file.Â  This is not a perfect solution, but it is ideal for the vast majority of cases, since you can just modify 'script.js' in place without a care in the world, and then reference your incrementing scripts to ensure cache refreshes.Â  Because HTTP caching operates on HTTP URIs, the fact that requests for 'script.js' and 'script_2.js' both hit the same file on disk is irrelevant; they're separate URIs, so they'll be cached separately.</p>
<p>That's not a solution in and of itself, however, because we still have to update all the SCRIPT tags to use a new filename (even though it'll end up hitting the same file on disk).Â  But now that the URI is divorced from the file itself, we don't have to keep anything in sync.</p>
<p>The last piece is to set up a global variable to use in your script suffixes:</p>
<pre>&lt;cfset application.scriptVersion = 2 /&gt;
...
&lt;script type="text/javascript" src="/path/to/script_#application.scriptVersion#.js"&gt;&lt;/script&gt;
</pre>
<p>Then any time you increment that scriptVersion variable, all your JS will suddenly become uncached and everyone will refresh.Â  So you can just hack away on script.js until you're happy, bump the variable up, and you're done.Â  No new files, no changing SCRIPT tags, super simple.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2010/03/10/fake-filenames-for-far-future-expires-headers/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
	</channel>
</rss>
