module AssetHat
module JS
# For working with supported 3rd-party JavaScript
# plugin/framework/library vendors.
module Vendors
# A list of supported 3rd-party JavaScript plugin/vendor names.
# Homepages:
#
# * Frameworks/libraries:
# * {Dojo}[http://dojotoolkit.org/]
# * {Ext Core}[http://extjs.com/products/extcore/]
# * {jQuery UI}[http://jqueryui.com/]
# * {jQuery}[http://jquery.com/]
# * {MooTools}[http://mootools.net/]
# * {Prototype}[http://www.prototypejs.org/]
# * {script.aculo.us}[http://script.aculo.us/]
# * {SWFObject}[http://code.google.com/p/swfobject/]
# * {WebFont Loader}[http://code.google.com/apis/webfonts/docs/webfont_loader.html]
# * {YUI}[http://developer.yahoo.com/yui/]
# * Loaders:
# * {LABjs}[http://labjs.com]
VENDORS_ON_GOOGLE_CDN = [
:dojo,
:ext_core,
:jquery,
:jquery_ui,
:mootools,
:prototype,
:scriptaculous,
:swfobject,
:webfont,
:yui
]
VENDORS_ON_CDNJS = [
:lab_js
]
VENDORS = VENDORS_ON_GOOGLE_CDN + VENDORS_ON_CDNJS
# Accepts an item from `VENDORS`, and returns the URL at which that
# vendor asset can be found. The URL is either local (relative) or
# remote, depending on the environment configuration:
#
# - If `AssetHat.consider_all_requests_local?` is true:
# - The local file takes precedence.
# - If the local file is missing, the remote URL in assets.yml is
# used as a fallback.
# - If there is no remote URL in assets.yml, the Google CDN URL is
# used as a fallback. (This makes setup easier: If the app doesn't
# already have a local copy of the vendor file, then it's instead
# loaded remotely.)
# - If `AssetHat.consider_all_requests_local?` is false:
# - The remote URL in assets.yml takes precedence.
# - The {Google CDN}[http://code.google.com/apis/ajaxlibs/] URL is
# used as a fallback, but only if a version number can be found
# (either in assets.yml or via the helper's `:version` option). If
# no version number is found, the remote URL cannot be built, so
# the local file (if any) is used as a fallback.
#
# Options:
#
# [ssl] Boolean for whether to include vendor JS via HTTPS. Defaults
# to false.
# [version] The vendor version, e.g., '1.6.0' for jQuery 1.6. By
# default, each vendor version is taken from
# config/assets.yml
; use this option to override
# the configuration.
def self.source_for(vendor, options={})
vendor_config =
AssetHat.config['js']['vendors'][vendor.to_s] rescue nil
use_local = AssetHat.consider_all_requests_local?
use_ssl = !!options[:ssl]
version = options[:version] || vendor_config['version'] rescue nil
# Prepare local path and default remote URL
srcs = Vendors.vendor_uris(vendor,
:use_ssl => use_ssl, :version => version)
local_src, remote_src = srcs[:local], srcs[:remote]
# Using the local URL requires that the vendor file exists locally. If
# the vendor file doesn't exist, use the remote URL as fallback.
use_local &&= AssetHat.asset_exists?(local_src, :js)
# If no version given, can't determine the remote URL; use the local
# URL as fallback.
use_local ||= version.blank?
if use_local
src = local_src
else
# To ease setup, if no local copy of the vendor code is found,
# use a remote URL as a fallback.
# Give precedence to configured remote URLs
src = vendor_config.try(:[], 'remote_ssl_url') if use_ssl
src ||= vendor_config.try(:[], 'remote_url')
# Use default remote URL as fallback
src ||= remote_src
# Use local URL as final resort, even though the file doesn't
# exist, in hopes that the app maintainer finds the 404 (or the
# warning below) in the logs. This needs to be fixed in the app,
# rather than relying on a CDN to dynamically provide the latest
# stable vendor version.
if src.blank?
src = local_src
Rails.logger.warn "\n\nAssetHat WARNING (#{Time.now}):\n" + %{
Tried to reference the vendor JS `:#{vendor}`, but
#{AssetHat.assets_dir(:js)}/#{local_src} couldn't be found, and
couldn't use a remote fallback because no vendor version was
given in #{AssetHat::RELATIVE_CONFIG_FILEPATH}.
}.squish!
# TODO: Create `AssetHat::Logger.warn`, etc. methods
end
end
src
end
private
def self.vendor_uris(vendor, options={})
# Returns a hash with keys `:local` and `:remote`.
uris = {}
use_ssl = options[:use_ssl]
version = options[:version]
case vendor
when :jquery
uris[:local ] = "#{['jquery', version].compact.join('-')}.min.js"
uris[:remote] = "http#{'s' if use_ssl}://ajax.googleapis.com/ajax/libs/jquery/#{version}/jquery.min.js"
when :jquery_ui
uris[:local ] = "#{['jquery-ui', version].compact.join('-')}.min.js"
uris[:remote] = "http#{'s' if use_ssl}://ajax.googleapis.com/ajax/libs/jqueryui/#{version}/jquery-ui.min.js"
when :prototype
# Prototype currently doesn't provide an official minified version.
uris[:local ] = "#{['prototype', version].compact.join('-')}.js"
uris[:remote] = "http#{'s' if use_ssl}://ajax.googleapis.com/ajax/libs/prototype/#{version}/prototype.js"
when :scriptaculous
# script.aculo.us currently doesn't provide an official minified version.
uris[:local ] = "#{['scriptaculous', version].compact.join('-')}.js"
uris[:remote] = "http#{'s' if use_ssl}://ajax.googleapis.com/ajax/libs/scriptaculous/#{version}/scriptaculous.js"
when :mootools
uris[:local ] = "#{['mootools', version].compact.join('-')}.min.js"
uris[:remote] = "http#{'s' if use_ssl}://ajax.googleapis.com/ajax/libs/mootools/#{version}/mootools-yui-compressed.js"
when :dojo
uris[:local ] = "#{['dojo', version].compact.join('-')}.min.js"
uris[:remote] = "http#{'s' if use_ssl}://ajax.googleapis.com/ajax/libs/dojo/#{version}/dojo/dojo.xd.js"
when :swfobject
uris[:local ] = "#{['swfobject', version].compact.join('-')}.min.js"
uris[:remote] = "http#{'s' if use_ssl}://ajax.googleapis.com/ajax/libs/swfobject/#{version}/swfobject.js"
when :yui
uris[:local ] = "#{['yui', version].compact.join('-')}.min.js"
uris[:remote] = "http#{'s' if use_ssl}://ajax.googleapis.com/ajax/libs/yui/#{version}/build/yuiloader/yuiloader-min.js"
when :ext_core
uris[:local ] = "#{['ext-core', version].compact.join('-')}.min.js"
uris[:remote] = "http#{'s' if use_ssl}://ajax.googleapis.com/ajax/libs/ext-core/#{version}/ext-core.js"
when :webfont
uris[:local ] = "#{['webfont', version].compact.join('-')}.min.js"
uris[:remote] = "http#{'s' if use_ssl}://ajax.googleapis.com/ajax/libs/webfont/#{version}/webfont.js"
when :lab_js
uris[:local ] = "#{['LAB', version].compact.join('-')}.min.js"
remote_host =
if use_ssl
'https://d3eee1nukb5wg.cloudfront.net/'
# This must match the value in the cdnjs repo:
# https://github.com/cdnjs/cdnjs/raw/master/https_location
#
# Amazon CloudFront doesn't support SSL, as discussed here:
# - http://www.cdnjs.com/#IDComment130405257
# - https://forums.aws.amazon.com/message.jspa?messageID=141951
# As a result, the SSL certificate at is
# invalid. To work around this, we instead load assets via
# cdnjs's CloudFront bucket ID. The bucket ID may change in
# the future, so it should be synced with the host published
# in the cdnjs repo, as noted above.
#
# For complete control over this, you can simply download the
# vendor JS and host it on a server where you can maintain
# SSL certificates.
else
'http://ajax.cdnjs.com'
end
uris[:remote] = "#{remote_host}/ajax/libs/labjs/#{version}/LAB.min.js"
else nil # TODO: Write to log
end
# The remote URL can only be properly determined if the version number
# is known; otherwise, discard.
uris.delete(:remote) if version.blank?
uris
end
# Usage (currently only supports LABjs):
#
# AssetHat::JS::Vendors.loader_js :lab_js,
# :urls => ['/javascripts/app.js',
# 'http://cdn.example.com/jquery.js']
#
# Returns a string of JS:
#
# window.$LABinst=$LAB.
# script('/javascripts/app.js').wait().
# script('http://cdn.example.com/jquery.js').wait();
def self.loader_js(loader, opts)
return nil unless opts[:urls]
case loader
when :lab_js
urls = opts[:urls]
lines = ['window.$LABinst=$LAB.']
urls.each_with_index do |url, i|
is_last = i >= urls.length - 1
lines << " script('#{url}').wait()#{is_last ? ';' : '.'}"
# Use `wait()` for each script to load in parallel, but
# preserve execution order by default. Alternatively, call
# `$LAB.setOptions({AlwaysPreserveOrder:true})` at the start
# of the chain, but if the list of bundles to include is short,
# this may use even more bytes.
end
lines.join("\n")
end
end
end
end
end