LaceySnr.com - Salesforce Development Posts by Matt Lacey

Managed Packages Rewrite CSS in Static Resources

Posted: 2021-08-11

Recently I was alerted to a bug in our application which on the surface seemed to be a simple problem, but as is often the case with development, things that appear simple are often anything bug. The primary purpose of our Managed Package is to provide an SPA (Single Page Application) that's served up using Visualforce, with a static resource containing all of the JavaScript, CSS and other assets. The bug was that something was rendering incorrectly. No worries there. What got weird, was that the when using the browser developer tools, I could see that the rules being applied in the client's org were slightly different to those being applied in my current scratch org, specifically an extra rule was being applied in the client org.

Wha?

Yeah, that's what I thought. No problem, I checked the latest code on our main branch, confirmed it was good as per my scratch org, re-built the static resource and deployed it to the packaging org. I packaged everything up, installed the new version of the package into the client's sandbox and verified the fix. Or at least, I expected to verify the fix. The problem with said verification, was that the fix wasn't there and the UI bug was. At this point my thought process went something like this:

  1. The code is good in my scratch org
  2. The code is good in the git repo
  3. The code is broken in the package
  4. We must be packaging broken code
  5. Our CI process must be doing something wrong in the build (it builds the zip file for the static resource from git)

Wrong

To verify my hypothesis (which I wasn't particulary confident in because when something is broken, it's almost never the tools) I downloaded the zip file for the static resource from the packaging org, extracted it, and ran a diff against the compiled CSS that I had generated locally on my own machine, and was known to be good. You can probably imagine my surprise when the 'broken' code from the packaging org was identical to the working code I had locally. At this point I was really confused, because I really didn't think the packaging process would modify anything, let alone a CSS file contained in a ZIP archive for a static resource. Why would it? Turns out it doesn't.

A Clue

The next step in my investigation involved me downloading the static resource from a the client's sandbox, extracting the archive and running a diff against my local copy again. This time, there were differences.

Exhibit A: Who Needs Comments Anyway?

The first change is something that is pretty easy to spot as the diff covers a significant chunk of the file: our whitespace of four spaces for indentation is replaced with two spaces (yes, should be minimised, turns out we did have a different bug in our build chain at the time). Seemingly innocent, but why is that even being done? Comments get stripped too:

Exhibit B: The Purpose

Looking further, I saw the one (intended) functional change: the web font URLs had an Org Id appeneded to them as a URL query parameter:

What's particularly interesting about this is that the Id isn't that of the packaging org, but in this case of the client's sandbox org. This means that CSS inside of a managed package is re-written at install time. This really blew my mind; packaging the code doesn't change it, but installing it does. Upon enquiring I got the following note back from a very trustworthy source:

Browsers made a change to never include cookies with font requests. Visualforce had to hot-patch this in to avoid regressing stuff.

Makes sense, but wow. I would not have wanted to be on the team trying to deal with that particular fix. The scope for mistakes would have been huge, but simply stripping comments and some whitespace at the same time couldn't affect things, could it?

Exhibit C: The Buggening

This image might be hard to see, so here's the difference in text form. This is the code before the package is installed:

[class^="icon-"]:before, [class*=" icon-"]:before {

Granted, this is a very specific selector, and probably prone to failure in other unforseen ways, but it was to deal with a third party inclusion (that as of the time of writing, is no longer included). But this is how it looks after installation:

[class^=icon-]:before, [class*=icon-]:before {

No big deal, the double quotes (") have gone. But that's not all. In the second selector, the whitespace has also been removed, and this changes how that selector matches CSS classes in the document. Ignoring the :before pseudo modifier, the key part is the class*=icon-] as it's rendered in the installed version of the code. This would match both this:

<div class="dna icon-fortytwo"></div>

and this:

<div class="dna zaphod-rocks-icon-fortytwo"></div>

The problem, is that it's more generic than our original code, which specifically includes the whitespace. The original code, [class*=" icon-"] would only match the first of the two examples above. This was the source of the bug, being more generic the re-written rule was being applied where it shouldn't have been, and that was why an extra rule was being applied to the DOM element in the client's org. It was fixed by tweaking the classes and selector to make them less fragile, easy enough once I knew what was actually going on.

Learn From My Mistake

The takeaways here are two-fold, though the first is probably the more imporant:

  1. Where you can, keep your CSS selectors simple. KISS.
  2. Don't assume that once something's packaged, it's etched in stone. It's more of a clay, or perhaps a soft sandstone maybe. Sometimes it it is the tools.