As a practicing masochist, I have recently developed an interest in third-party JavaScript application development. I’m not alone: third-party JavaScript (or “3PJS”) applications are becoming more and more common on the web (see Disqus, Optimizely, and Google Analytics, for instance). Like any developing methodology, best practices are constantly being explored and re-defined. In this article, I’d like to suggest an approach to styling content in third-party JavaScript applications.
Many 3PJS applications insert content into the publisher’s DOM, and developers often wish to control the presentation of this content. Like most tasks in web development, this is easier said than done. The two immediately obvious approaches (defining styles in external stylesheets and inlining styles on the DOM elements themselves) are sub-optimal–here’s why:
External Stylesheets
The benefits of external stylesheets should be familiar to most web developers. As a recap:
- Maintainability (clear separation between logic and presentation)
- Recognize designer’s workflow (this is NOT just a talking point–it’s important!)
- Support CSS preprocessors [1] [2] [3]
Normally, this would be the end of the discussion. Unfortunately for you, you’re writing a Third-Party JavaScript application. Here are the bad parts:
- Require an additional HTTP request
- Very difficult to reliably detect when loading is complete*
*There are a number of methods for dynamic CSS loading [4] [5] [6] [7] [8] [9], but I have yet to find an approach that is: (1) scalable, (2) cross-browser compliant, and (3) error-tolerant. It looks like help is on the way, though.
Inline Styles
Given these drawbacks in third-party scenarios, why not define inline styles (i.e. <p style="margin: 2em;"></p>
)? Here are the benefits:
- Limit HTTP requests
- Less code required (no need for buggy CSS loading logic)
The drawbacks are well-known to most developers:
- Terrible to maintain
- Awkward for designers
- Incompatible with CSS preprocessors
Building CSS into the JavaScript
If you aren’t already building your JavaScript files, you should be. It allows you to automate the minification process (see UglifyJS) and catch bugs with static code analysis (see JSHint). In this article, I’d like to demonstrate how a build process can also give you the ability to automatically package presentation and logic into one valid JavaScript file. There are many ways you might accomplish this. I’ve outlined source CSS and JavaScript files below:
src/widget1.css
div.container {
font-family: "Century Gothic", sans-serif;
/* place below other elements */
z-index: -1;
}
src/widget1.js
(function() {
var container = $("<div>").attr("style", "!import_rule div.container");
// The rest of your application...
})();
by making your build process aware of the !import_rule [rule name]
syntax, you can automatically build a JavaScript file that contains the CSS in-line.
For example, building “widget1” would generate the following JavaScript file:
dist/widget1.js
(function() {
var container = $("<div>").attr("style", "font-family: \"Century Gothic\", sans-serif;z-index: -1;");
// The rest of your application...
})();
Note that the inclusion syntax could really be formatted in any arbitrary (but consistent) manner. Also, the examples in this article assume an intermediate step of CSS minification. I strongly recommend this; here are a few tools to consider: YUI CSS Compressor, Free CSS Compressor, and Rainpress.
Drawbacks
If you don’t currently have a build process for your JavaScript files, utilizing this method is going to introduce a new step to your workflow. This is a Bad Thing, but there are many benefits to maintaining a build process besides bundling CSS. Among them (as mentioned above) are: static code analysis, CSS preprocessing, and minification.
Of course, style information cannot be independently cached with this approach. If you intend to share styles across multiple JavaScript files, shipping a cache-able stylesheet as a separate file may be preferable.
Also, while the include statement may look like CSS, it is really an emulation.
This means that CSS authors must be aware that, while a rule like “div.container” may be built as expected, it does not strictly follow that a rule for “div” is going to apply to all <div>
elements in the JavaScript file.
Even more troublesome is that with this approach, dynamically modifying styles (toggling, combining, etc.) is not a simple matter of adding and removing classes.
To achieve similar results, you will have to re-set element styling wholesale (in the best case), and perform involved string operations (in the worst).
A More Robust Approach
That last drawback ought to be a deal-breaker for most developers.
There is a more conservative solution: building entire CSS files into a single JavaScript string and injecting with a <style>
tag.
To accomplish this task, you could define a new directive: !import_file [file name]
. Using this, your build process could use the source files:
src/widget2.css
div.widget2-container {
font-family: "Century Gothic", sans-serif;
/* place below other elements */
z-index: -1;
}
div.widget2-container h1 {
color: #a00;
}
src/widget2.js
(function() {
var styleElem = $("<style>" + "!import_file widget2.css" + "</style>");
// The rest of your application...
})();
…to build the following JavaScript:
dist/widget2.js
(function() {
var styleElem = $("<style>" + "div.widget2-container { font-family: \"Century Gothic\", sans-serif;z-index: -1; } div.widget2-container h1 { color: #a00; }" + "</style>" );
// The rest of your application...
})();
This method preserves the semantics of the CSS file while also avoiding an additional request for style information (along with associated load event detection difficulties).
Due to CSS specificity rules, rules declared in a <style>
tag are more susceptible to publisher over-riding.
Fortunately, there are additional steps that mitigate this risk (more on this in an upcoming article).
And just like inclusion via <link>
tag, your styles will be defined over the whole page, so make sure you properly namespace IDs and class names (e.g. .widget2-container
in the example above) to avoid collisions.
Try It Out!
I’ve implemented the described build step in Ruby, Perl, and NodeJS. Each of the command-line scripts take any number of file names as arguments (JavaScript and CSS).
They output built JavaScript to standard out, so you can >
to your heart’s content.
Play around with the scripts, and please give me your feedback. I’m particularly interested to hear how this process would enhance/break your workflow, and how it might be extended or otherwise improved. If you’re interested in reading more about third-party JavaScript development, check out the upcoming book Third Party JavaScript by Anton Kovalyov and Ben Vinegar.
Finally, I’ve written more on CSS in 3PJS in an article on defensive techniques for declaring styles in publisher environments.