Skip to content

Instantly share code, notes, and snippets.

@tbranyen
Last active August 29, 2015 13:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tbranyen/9667269 to your computer and use it in GitHub Desktop.
Save tbranyen/9667269 to your computer and use it in GitHub Desktop.
AMD Feature

AMD Feature is a plugin that makes it easier to substitute modules based on a feature test. The easiest way to explain is through an example. Lets say you have a full featured plugin that is designed for a desktop browser, but when on a mobile device you want to load a similar plugin that is much lighter. AMD Feature allows you to describe the runtime conditions that determine which plugin would be loaded from a single call. Another use case could be something like jQuery/Zepto based on browser environment.

Inspired by: https://github.com/jensarps/AMD-feature

// Configure the AMD implementation.
require({
// Options are specified under the `feature` key.
feature: {
// This is the name you will actually require: `require('feature!mode')`.
mode: {
// The keys are the actual modules that are looked up if the condition
// within the function is truthy.
"mode/browser": function() {
return typeof window === "object";
},
// If your tests are bad and both of these are truthy, the first one
// encountered will be returned. This could have unexpected results
// if you were expecting the second.
"mode/node": function() {
return typeof process === "object" && process.title === "node";
}
}
}
});
// When required from a browser.
require(["feature!mode"], function(mode) {
// mode is the exports from "mode/browser"
});
// When required from Node.js.
require(["feature!mode"], function(mode) {
// mode is the exports from "mode/node"
});
/* AMD Feature Plugin v0.1.0
* Copyright 2013, Tim Branyen (@tbranyen), based on original work by jensarps.
* feature.js may be freely distributed under the MIT license.
*/
(function(global) {
// Cache used to map configuration options between load and write.
var buildMap = {};
define({
version: "0.1.0",
// Invoked by the AMD builder, passed the path to resolve, the require
// function, done callback, and the configuration options.
load: function(name, req, load, config) {
// Dojo provides access to the config object through the `req` function.
if (!config) {
config = require.rawConfig;
}
if (!config.feature) {
return;
}
// Detect the specific feature.
var feature = config.feature[name];
// No feature to load, throw.
if (!feature) {
throw new TypeError("Feature '" + name + "' is undefined or does not" +
" have a `feature` config. Make sure it exists, add a `feature`" +
" config, or don't use feature! on it");
}
// Attach to the build map for use in the write method below.
buildMap[name] = { feature: feature };
// Okay so yea, technically keys could be out of order. We all know this,
// but sometimes you just gotta reach for whatever and do whatever.
for (var name in feature) {
// Ensure we only iterate over Object properties.
if (!feature.hasOwnProperty(name)) { continue; }
// Get the callback to test.
var callback = feature[name];
// In a build context, do not load any features.
if (config.isBuild) {
load();
continue;
}
// Test the callback, use the first one to pass. If a callback was not
// provided, try testing for truthiness of the value.
if (typeof callback === "function" ? callback.call(global) : callback) {
// Bring in the correct module.
req([name], load);
// End looping.
break;
}
}
},
// Also invoked by the AMD builder, this writes out a compatible define
// call that will work with loaders such as almond.js that cannot read
// the configuration data.
write: function(pluginName, featureName, write) {
var feature = buildMap[featureName].feature;
// Add the starting JSON bracket.
var features = ["{"];
// Convert all test functions to strings.
for (var name in feature) {
// ensure we only iterate over object properties.
if (!feature.hasOwnProperty(name)) { continue; }
// Add this feature to the source.
features.push("'" + name + "': " + feature[name].toString() + ",");
}
// Remove the last comma.
features[features.length-1] = features[features.length-1].slice(0, -1);
// Add the last bracket.
features.push("}");
function findFeature() {
for (var name in feature) {
// Ensure we only iterate over object properties.
if (!feature.hasOwnProperty(name)) { continue; }
// Get the callback to test.
var callback = feature[name];
// Test the callback, use the first one to pass. If a callback was not
// provided, try testing for truthiness of the value.
if (typeof callback === "function" ? callback.call(window) : callback) {
// Bring in the correct module.
return require(name);
}
}
}
// Write out the actual definition
write([
"define('", pluginName, "!", featureName, "', [], function() {",
// Set all the features.
"var feature = ", features.join(""), ";",
// Find the correct feature and auto-run it.
"return (", findFeature.toString(), ")", "();",
"});\n"
].join(""));
}
});
})(typeof global === "object" ? global : this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment