Created
March 14, 2014 03:09
-
-
Save unscriptable/9541495 to your computer and use it in GitHub Desktop.
wire-cram plugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** @license MIT License (c) copyright B Cavalier & J Hann */ | |
/** | |
* wire/cram/builder plugin | |
* Builder plugin for cram | |
* https://github.com/cujojs/cram | |
* | |
* wire is part of the cujo.js family of libraries (http://cujojs.com/) | |
* | |
* Licensed under the MIT License at: | |
* http://www.opensource.org/licenses/mit-license.php | |
*/ | |
(function(define) { | |
define(function(require) { | |
var when, unfold, mid, defaultModuleRegex, defaultSpecRegex, replaceIdsRegex, | |
removeCommentsRx, splitIdsRegex; | |
when = require('when'); | |
unfold = require('when/unfold'); | |
mid = require('../lib/loader/moduleId'); | |
// default dependency regex | |
defaultModuleRegex = /\.(module|create)$/; | |
defaultSpecRegex = /\.(wire\.spec|wire)$/; | |
// adapted from cram's scan function: | |
//replaceIdsRegex = /(define)\s*\(\s*(?:\s*["']([^"']*)["']\s*,)?(?:\s*\[([^\]]+)\]\s*,)?\s*(function)?\s*(?:\(([^)]*)\))?/g; | |
//replaceIdsRegex = /(define)\s*\(\s*(?:\s*["']([^"']*)["']\s*,)?(?:\s*\[([^\]]*)\]\s*,)?/; | |
replaceIdsRegex = /(\bdefine)\s*\(\s*(?:\s*'([^']*)'|"([^"]*)"\s*,)?(?:\s*\[([^\]]*)\]\s*,)?/; | |
removeCommentsRx = /\/\*[\s\S]*?\*\//g; | |
splitIdsRegex = /\s*,\s*/; | |
return { | |
normalize: normalize, | |
compile: compile | |
}; | |
function normalize(resourceId, toAbsId) { | |
return resourceId ? toAbsId(resourceId.split("!")[0]) : resourceId; | |
} | |
function compile(wireId, resourceId, require, io, config) { | |
// Track all modules seen in wire spec, so we only include them once | |
var specIds, defines, seenModules, childSpecRegex, | |
moduleRegex; | |
defines = []; | |
seenModules = {}; | |
moduleRegex = defaultModuleRegex; | |
childSpecRegex = defaultSpecRegex; | |
// Get config values | |
if(config) { | |
if(config.moduleRegex) moduleRegex = new RegExp(config.moduleRegex); | |
if(config.childSpecRegex) childSpecRegex = new RegExp(config.childSpecRegex); | |
} | |
// Grab the spec module id, *or comma separated list of spec module ids* | |
// Split in case it's a comma separated list of spec ids | |
specIds = resourceId.split(splitIdsRegex); | |
return when.map(specIds, function(specId) { | |
return processSpec(specId); | |
}).then(write, io.error); | |
// For each spec id, add the spec itself as a dependency, and then | |
// scan the spec contents to find all modules that it needs (e.g. | |
// "module" and "create") | |
function processSpec(specId) { | |
var dependencies, ids; | |
dependencies = []; | |
ids = [specId]; | |
_addDep(wireId); | |
return unfold(fetchNextSpec, endOfList, scanSpec, ids) | |
.then(function() { | |
return generateDefine(specId, dependencies); | |
} | |
); | |
function fetchNextSpec() { | |
var id = ids.shift(); | |
return when.promise(function(resolve, reject) { | |
require( | |
[id], | |
function(spec) { resolve([{ spec: spec, id: id }, ids]); }, | |
reject | |
); | |
}); | |
} | |
function _addDep(moduleId) { | |
if(!(moduleId in seenModules)) { | |
dependencies.push(moduleId); | |
seenModules[moduleId] = moduleId; | |
} | |
} | |
function scanSpec(specDescriptor) { | |
var spec = specDescriptor.spec; | |
scanPlugins(spec); | |
scanObj(spec); | |
function resolveId(moduleId) { | |
return mid.resolve(specDescriptor.id, moduleId) | |
} | |
function scanObj(obj, path) { | |
// Scan all keys. This might be the spec itself, | |
// or any sub-object-literal in the spec. | |
for (var name in obj) { | |
scanItem(obj[name], createPath(path, name)); | |
} | |
} | |
function scanItem(it, path) { | |
// Determine the kind of thing we're looking at | |
// 1. If it's a string, and the key is module or create, then assume it | |
// is a moduleId, and add it as a dependency. | |
// 2. If it's an object or an array, scan it recursively | |
// 3. If it's a wire spec, add it to the list of spec ids | |
if (isSpec(path) && typeof it === 'string') { | |
addSpec(it); | |
} else if (isDep(path) && typeof it === 'string') { | |
// Get module def | |
addDep(it); | |
} else if (isStrictlyObject(it)) { | |
// Descend into subscope | |
scanObj(it, path); | |
} else if (Array.isArray(it)) { | |
// Descend into array | |
var arrayPath = path + '[]'; | |
it.forEach(function(arrayItem) { | |
scanItem(arrayItem, arrayPath); | |
}); | |
} | |
} | |
function scanPlugins(spec) { | |
var plugins = spec.$plugins || spec.plugins; | |
if(Array.isArray(plugins)) { | |
plugins.forEach(addPlugin); | |
} else if(typeof plugins === 'object') { | |
Object.keys(plugins).forEach(function(key) { | |
addPlugin(plugins[key]); | |
}); | |
} | |
} | |
function addPlugin(plugin) { | |
if(typeof plugin === 'string') { | |
addDep(plugin); | |
} else if(typeof plugin === 'object' && plugin.module) { | |
addDep(plugin.module); | |
} | |
} | |
function addDep(moduleId) { | |
_addDep(resolveId(moduleId)); | |
} | |
function addSpec(specId) { | |
specId = resolveId(specId); | |
if(!(specId in seenModules)) { | |
ids.push(specId); | |
} | |
_addDep(specId); | |
} | |
} | |
} | |
function generateDefine(specId, dependencies) { | |
var dfd, buffer; | |
dfd = when.defer(); | |
io.read(ensureExtension(specId, 'js'), function(specText) { | |
buffer = injectIds(specText, specId, dependencies); | |
defines.push(buffer); | |
dfd.resolve(); | |
}, dfd.reject); | |
return dfd.promise; | |
} | |
function write() { | |
// protect against prior code that may have omitted a semi-colon | |
io.write('\n;' + defines.join('\n')); | |
} | |
function isDep(path) { | |
return moduleRegex.test(path); | |
} | |
function isSpec(path) { | |
return childSpecRegex.test(path); | |
} | |
} | |
function createPath(path, name) { | |
return path ? (path + '.' + name) : name | |
} | |
function isStrictlyObject(it) { | |
return (it && Object.prototype.toString.call(it) == '[object Object]'); | |
} | |
function ensureExtension(id, ext) { | |
return id.lastIndexOf('.') <= id.lastIndexOf('/') | |
? id + '.' + ext | |
: id; | |
} | |
function injectIds(moduleText, absId, moduleIds) { | |
var replaced, newText; | |
// note: replaceIdsRegex removes commas, parens, and brackets | |
newText = moduleText.replace(removeCommentsRx, '').replace(replaceIdsRegex, function (m, def, mid1, mid2, depIds) { | |
replaced = true; | |
// merge deps, but not args since they're not referenced in module | |
if(depIds) { | |
moduleIds = depIds.split(splitIdsRegex).concat(moduleIds); | |
} | |
moduleIds = '[' + moduleIds.map(quoted).join(', ') + '], '; | |
return def + '(' + quoted(absId) + ', ' + moduleIds; | |
}); | |
if (!replaced) { | |
throw new Error('Unable to parse AMD define() in ' + absId); | |
} | |
return newText; | |
} | |
function quoted(id) { | |
return '"' + id + '"'; | |
} | |
function endOfList(ids) { | |
return !ids.length; | |
} | |
}); | |
}(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(require); })); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment