Skip to content

Instantly share code, notes, and snippets.

@rmurphey
Last active August 29, 2015 14:19
Show Gist options
  • Save rmurphey/6842b3b1b806dd123676 to your computer and use it in GitHub Desktop.
Save rmurphey/6842b3b1b806dd123676 to your computer and use it in GitHub Desktop.
Proposed: JS Static Resources Service

Problem Statement

Bazaarvoice has multiple consumer-facing web applications; each of these apps uses some set of vendor resources -- for example, jQuery or Backbone. It is possible for multiple applications to be on the same page; when this happens, each application brings its own vendor resources. This means that Bazaarvoice applications may end up loading the same resource multiple times on the same page, which has a negative impact on performance.

Proposal

Consumer-facing web applications created by Bazaarvoice should be able to share vendor code. To achieve this, we would create the following pieces, discusssed in detail below:

  • A static resources service (e.g. display.static.bazaarvoice.com)
  • A client-side JS module for requesting vendor code from the service, intended for inclusion in the scout file
  • A Grunt (or similar) tool for populating the service

The Static Resources Service

The Static Resources Service would provide a /vendor/*.js endpoint for requesting vendor modules. This endpoint would allow a consumer application to request multiple vendor modules, e.g. /vendor/backbone@1.1+underscore@2.4. It would return a file containing all requested resources, exposed via the predefined callback method BV._vendor.define.

(function () {

BV._vendor.define('underscore@2.4', [], function () {
  return { /* ... */ };
});

BV._vendor.define('backbone@1.1', [ 'underscore', 'jquery' ], function (_, $) {
  return { /* ... */ };
});

}());

Implementation

The service would be implemented as an s3 bucket with Akamai or CloudFront CDN services. See below for details on how the service would be populated.

The Client-Side JS Module

Applications would use a client-side JS module to request vendor modules from the service.

The module would provide an interface such as the following:

BV._vendor.require(['jquery@1.8', 'backbone@1.1', 'underscore@2.4'], function ($, Backbone, _) {
  // ...
});

Given this request, the module would:

  • Determine whether the modules were already available, by inspecting window.BV._vendor.
  • Create a canonical URL for requesting unavailable resources from the service, ordering the requested modules alphabetically in order to facilitate caching:
https://display.static.bazaarvoice.com/vendor/backbone@1.1+underscore@2.4.js
  • Use the response from the service to call the provided callback

The module would be responsible for defining the BV._vendor.register callback that the service uses in its response.

It may be advisable for the module to maintain a list of known-good package names, in order to prevent errors.

Populating the Service

The service would be populated with static files generated via a Grunt-based tool (or similar). This tool would consume a configuration such as the following:

var packages = [
  [ 'jquery@1.8', 'backbone@1.1', 'lodash@2.4' ],
  // ...
];

In the near term, teams would contribute to this configuration (via pull request) to express the dependencies they would need. Importantly, the tool would not just build the exact combinations requested; it would also build all subsets of each combination. So, if the packages array contained an entry for [ 'jquery@1.8', 'backbone@1.1', 'lodash@2.4' ], then the tool would create files for the following (always ordering the modules alphabetically):

  • backbone@1.1,jquery@1.8,lodash@2.4
  • backbone@1.1,jquery@1.8
  • jquery@1.8,lodash@2.4
  • backbone@1.1,lodash@2.4
  • jquery@1.8
  • backbone@1.1
  • lodash@2.4

This covers the case where an application requests ['backbone@1.1', 'jquery@1.8', 'lodash@2.4'] but the loader discovers that one of the resources is already available, and thus only loads the two unavailable resources.

The tool would be responsible for maintaining a mapping of module/version to the code to be included. In most cases, this mapping would simply point to an NPM module; in some cases, it might point to a private Bazaarvoice repo containing an NPM module.

Usage Notes

  • Applications should only make one request to the service per page load.
  • The request to the service should happen from the application's scout file whenever possible, in order to ensure the vendor resources arrive as soon as possible.
  • Applications would be responsible for handling failure cases, timeouts, etc.

Discussion

Some areas require further consideration and discussion:

  • What should the TTL be for these files? We should design the system such that the TTL can be extremely long.
  • How should we handle vendor files that have been modified by Bazaarvoice in order to address issues? We should determine naming and versioning strategies for these files (and avoid creating them at all when possible).
  • It's likely that the service should exist in QA as well as in PROD. I don't think there's a need for a STG service but I'm open to opinions.
  • This service could be used by non-consumer-facing applications as well.
@markmarkoh
Copy link

I wonder if this can be NPM backed instead of custom-s3 bucket backed. Instead of BV stating "We support the following 5 libraries", just support anything in NPM and have our service grab the static resources from NPM (preferably once) and cache that version.

That might be a more universally usable service (not that we have an obligation for this to be anything more than a custom solution for BV).

It will also answers Ben's question above - all vendor code would be fetched by this service.

@reason-bv
Copy link

If NPM-based we'd absolutely need to be running our own proxy registry - NPM isn't stable enough to be relied upon, newly funded or not.

There is a strong incentive to craft something here that doesn't use any servers that we must maintain. As soon as you add any of those it starts to tip the balance towards not worth the effort.

@rmurphey
Copy link
Author

Thanks for the fantastic feedback, this has been a great discussion so far. Here's where I am with this right now:

  • After discussion with various folks, it's clear we need to maintain BV-specific vendor code -- for example, Firebird has had to make BV-specific patches to Backbone and jQuery (and those patches should really be used by all 3pjs apps at BV). Along with the reasons that Reason cited, this takes NPM off the table, and I'm actually OK with that. We can maintain a repo with supported vendor modules/versions in it; that repo can follow along with the public versioning, but tack a -bv on the end perhaps. This actually makes the implementation a lot easier, I think.
  • We don't have great visibility right now into the versions of vendor code that various BV projects are using. That means that yes, to start with, there would be minimal bang for the buck -- though there are certainly benefits to making vendor code separate and more cacheable than application code. The roadmap here is to build the system (I think this is actually one of the easier ideas we've had!); get Curations, Firebird, and Spotlights using it; and then work to reconcile the versions they each use. My hope is that the potential savings of 10s of K will be compelling.
  • Anything that is unlikely to change often, and potentially shareable among projects, is a candidate for inclusion in this service -- honestly, it's not just vendor code.

@rmurphey
Copy link
Author

Here are the versions I found for a few different libraries:

jQuery

Spotlights: Not used
Curations Templates: 1.8.3 with modifications
Firebird: 1.11.1 with modifications

Backbone

Spotlights: 1.1.2 with modifications
Curations Templates: unused
Firebird: 1.0.0 with modifications

Moment.js

Curations Templates: 2.6.0
Firebird: 1.7.2
Spotlights: Not (yet) used

Lodash/Underscore

Firebird: Lodash 1.2.0 with modifications
Curations Templates: Underscore 1.5.2
Spotlights: Lodash 2.4.1

@lawnsea
Copy link

lawnsea commented Apr 20, 2015

@joeslice said:

What about when a developer wants to debug using source? Will you have a -min and not-min version?

Good question. We should provide source maps for all files.

@msmolev
Copy link

msmolev commented May 11, 2015

Should we think about plugging PRR/Agrippa into this system as well? I know they use jQuery with modifications, so theoretically that could be one less jQuery (but then... are all "with modifications" automatically mean "not compatible with what other apps want"?)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment