SouthStreet Progressive Enhancement Workflow
Our tools and workflow for building fast and accessible cross-device web applications
SouthStreet is a set of tools that combine to form the core of a progressive enhancement workflow developed at Filament Group. This workflow is designed to help us (and other developers too!) deliver rich web experiences that are accessible to the widest range of devices possible, and catered to the capabilities and constraints of each device.
Our SouthStreet workflow utilizes the following tools, all of which are independent Github projects themselves.
- eCSSential: an experimental utility for making browsers load responsive CSS in a more responsible way.
- QuickConcat: a simple dynamic concatenator for html, css, and js files, written in PHP
- AjaxInclude: a plugin that is designed for modular content construction, that runs on jQuery (or our yet-to-be released Shoestring)
- Picturefill: a simple pattern for overhead-free responsive images today.
Together these tools form the core of Filament Group's progressive enhancement workflow. The scope of these individual projects vary widely, but they all share a common goal of serving front-end code faster, either by preventing or deferring the loading of code and assets that are not essential to the device, or by offering light-weight alternatives to common patterns.
Please note also that we often use these projects in conjunction with other related tools that don't necessarily fit under the SouthStreet umbrella. Check out the related projects at the bottom for more of these.
For demonstration purposes, the
_tmpl folder of this repository contains a working example of these tools working together.
Please note that while these tools do represent key components of our overall approach, their applicability to a particular project always varies, and these particular projects are not always the best tool for the job at hand. Depending on whether a particular tool makes sense for the problem we're solving, we will often use alternative tools that provide similar functionality instead. For example, we commonly use jQuery instead of Shoestring (below), as Shoestring provides a small subset of jQuery's featureset, and is not always appropriate for the needs of our projects. In essence, the projects in SouthStreet are developed with a goal of ease of use and compatibility, but they should always be evaluated against other potential solutions.
Let's break down the role that each one plays.
Typically, a site that uses Enhance will start by including (anywhere in the page, or in the
enhance.js, and a custom file that uses the
enhance.js API to configure and enhance the user experience (or not) based on various conditions: for example purposes, we'll call that custom file
enhance.config.js. The role of
enhance.config.js is to determine if – and with which files – a browser's experience should be enhanced. Within
enhance.config.js, the following steps might be taken:
- Determine if a browser is broadly qualified enhancements and if not, exit early (a broad qualification might consist of detecting
document.querySelectorAllsupport, CSS3 Media Queries support, or any other technology critical to an application's enhanced experience)
- Queue certain files for loading based on various environmental conditions, browser capabilities, screen size, markup conditions, and more.
- Enhance the page by loading those files via a single, concatenated request.
enhance.config.js file in this repository.
head of the page, through a traditional
style tag, or use eCSSential, explained below. This limitation means
All of these tasks can be facilitated simply through the
api. However, Enhance.js itself does not handle the server-side concatenation that it is designed to interface with. Nor does it handle the application of enhancements itself. We'll get to those in a bit...
eCSSential is an experimental workaround to address this shortcoming. Unlike Enhance, eCSSential is designed to be used via an inline script tag in the
head of a page. This is because we want it to execute as soon as possible (thus speeding up page rendering), and because it is small enough that it's arguably worth the tradeoff in cacheability that an external resource would provide.
eCSSential provides many features, but the default use-case is to drop it into the
head of a page and call the
eCSSential() function, passing in references to your available CSS files paired with media queries to specify their intended media context. eCSSential will parse through these and split the CSS into files that should be loaded immediately to apply in initial page rendering, and files that can be loaded lazily after the page has been shown.
eCSSential does not require the use of concatenated CSS files, but for best performance, it is designed to be used in combination with them. With that, we'll move on to concatenation.
QuickConcat is a simple dynamic concatenator for html, css, and js files, written in PHP. Interacting with QuickConcat is simple: send it a URL containing several comma-separated filepaths, and it will combine those files and return them as a single response. It has a few simple features, described in its README, but basically, a QuickConcat URL looks something like the following:
Or better yet...
That's pretty much it; you can find the
quickconcat.php source code along with more examples and implementation notes in the QuickConcat project readme.
Within our PE workflow, QuickConcat is used by
enhance.config.js like so:
...and the necessary CSS files
Like many of the tools that comprise the Enhance pattern, QuickConcat is just as much a functional tool as it is a suggested pattern - the technology behind the implementation is less important than the workflow it facilitates. For small-scale production environments, quickconcat.php itself may be a suitable tool for use in a live website. However, at Filament Group, we typically only use QuickConcat during the initial development phase of a project, as it is easy to configure and get working quickly but does not include features for serving files quickly in a large-scale production environment. Early in a development phase, we commonly advise clients in building a custom file concatenation service similar to QuickConcat, but more robust and using their preferred languages, so that it can integrate tightly with their system in ways QuickConcat does not (at least by default).
In that vein, we recommend that a dynamic file concatenation tool provides at least the following services when deployed in a large-scale application:
- Dynamic file concatenation via URL, combining separate files into one response via a comma-separated request (QuickConcat does this)
- Transfer the output file in GZIP compressed format. Most server-side environments provide tools for gzip output (see the QuickConcat Readme for an example using Apache)
- When a particular combination of files is requested, its output should be saved as a static resource on the site's server or CDN, and all future requests should be directed straight to that file instead of dynamically generating it again. In this way, different devices will generate the various file combinations, and the second time a particular browser/device combo visits the site, the server can deliver that file much more efficiently. We also recommend pre-generating common file combinations during deployment so that many popular combinations will never need to be generated dynamically during a request
- For use with AjaxInclude (explained below), we recommend that the concatenation tool includes a feature to wrap each file contents in an identifier node, if requested to do so. For more information on this, please see "Configuring a concatenation tool to work with AjaxInclude" below.
quickconcat.php covered, we can move on to the actual enhancements.
AjaxInclude works by referencing external fragments of HTML content via HTML5 data attributes. For example:
<a href="articles/latest/" data-before="articles/latest/fragment">Latest Articles</a>
In this case, we have an ordinary link to external content, which is essential for accessibility across all devices, but the link is also tagged with a
data-before attribute that references a URL that contains a fragment of that external content to pull into the page. The AjaxInclude plugin will see this and include that content before the link.
You can add these attributes to elements in your page anywhere non-essential fragments of content can be included from an external URL. jQuery-api-like qualifiers like
data-replace are all supported. Also, the
data-threshold attr allows a min width for this to apply.
Note: these attributes can be placed on any element, not just anchors. You might find
data-append to be most useful on container elements.
Once the DOM is ready, you can apply the plugin like this:
Perhaps the most powerful feature of AjaxInclude is that it can be used with a proxy file concatenator (such as quickconcat) to fetch ALL includes via a single HTTP request! To use a proxy and include all ajax includes in one call, just pass in a URL that is ready to accept a list of files:
$("[data-append],[data-replace],[data-after],[data-before]").ajaxInclude( "quickconcat.php?wrap&files=" );
Configuring a concatenation tool to work with AjaxInclude
AjaxInclude expects the concatenator's response to wrap each HTML file in an identifier element like this:
<entry url="..file url...">..content...</entry>. That way, AjaxInclude can know which piece of HTML came from which file, and insert them in the proper places in the document. With QuickConcat, this is as simple as adding a
&wrap parameter to the query string. Because of the benefits this provides the AjaxInclude technique, we recommend that this functionality be built as part of a dynamic concatenation tool as well, in the event that QuickConcat is not sufficient for production. For more information on how this works, check out the quickconcat docs.
Picturefill was originally developed to match a proposed
picture element's behavior, but since the
picture element is currently - and potentially will always be - non-standard, we have developed a
div-based approach that we'd recommend for use today.
Picturefill allows us to reference several sources for a particular image in an HTML document, and based on which source's media query matches, Picturefill will load only one image appropriate to that context (and reevaluate whenever the viewport dimensions change as well).
Related Filament Group Projects
Filament Group actively develops and contributes to several other projects that we often find useful in our work. These aren't part of SouthStreet specifically, but you may find them useful:
- Respond.js: CSS3 Media Query support for Internet Explorer 6-8.
- Hide Address bar: Normalized address bar hiding for iOS & Android
- Fixed-fixed: CSS position:fixed qualifier.
- Overthrow: A tiny, no-frills, framework-independent, targeted overflow: auto polyfill for use in responsive design.
- RWD Nav Patterns: Responsive navigation patterns.
- Details Polyfill: A polyfill for the HTML5 details element.
- iOS OrientationChange Fix: A workaround for the orientation change zoom bug found in iOS5 and older versions.
- Device Bugs: An open bug tracker for issues found in browsers and devices.
- jQuery Mobile: The jQuery Mobile Framework.
- jQuery Mobile Pagination: Swipe-based website navigation for jQuery Mobile.
- Drive-In: A device testing theatre.
- Dynamic Carousel: A carousel plugin built for responsive layouts.
- jQuery Pixel to Em Converter: Like it says on the tin.
- jQuery Visualize: Accessible data visualization.
- jQuery Tree Control: An accessible tree control plugin.
- jQuery Custom Input: Custom, accessible checks and radios.
- jQuery Accessible Slider: An accessible select/input-to-custom slider control.
You can find more at our website as well. Filament Group, Inc