fid-umd
Version:
Easily add UMD to your JavaScripts so they can be loaded with CommonJS, AMD (RequireJS), Rhino, node.js, module.js, in the browser and everywhere else.
378 lines (296 loc) • 8.82 kB
JavaScript
/**
* FidUmd - UMD generator to wrap your JavaScript modules
*
* @module FidUmd
* @license MIT
*/
;
var Config, moduleSystemObjects;
// Prepare our list of module systems in the order we wish to use them
moduleSystemObjects = [
require("./modules/nodejs"),
require("./modules/commonjs"),
require("./modules/amd"),
require("./modules/modulejs"),
require("./modules/yui"),
require("./modules/root")
];
Config = require("./config");
/**
* Create an object that can wrap code in UMD headers/footers
*
* @constructor
* @alias module:FidUmd
* @property {number} counter Used for generating unique variables in tryTo()
*/
function FidUmd() {
if (!(this instanceof FidUmd)) {
return new FidUmd();
}
}
/**
* Creates configured module system instances
*
* @param {Config} config
* @return {Array.<Object>}
*/
FidUmd.prototype.configureModuleSystems = function (config) {
return moduleSystemObjects.map(function (System) {
return new System(config);
});
};
/**
* Create the postamble string. This is almost always at the end of
* the file
*
* @return {string}
*/
FidUmd.prototype.createPostamble = function () {
return " // fid-umd post\n}));\n// fid-umd post-end\n";
};
/**
* Create a preamble string
*
* config is an object similar to this
*
* // No dependencies
* config = {
* name: "MyObject"
* }
*
* // Dependency on OneOne and TwoTwo
* // For node, OneOne is an external library, TwoTwo is something local
* config = {
* name: 'HorseRacingTrack',
* depends: [
* 'OneOne',
* 'TwoTwo'
* ]
* }
*
* @param {!Config} config Controls dependencies and exported name
* @return {string} Preamble
*/
FidUmd.prototype.createPreamble = function (config) {
var code, dependencies, i, moduleSystems, preamble;
moduleSystems = this.configureModuleSystems(config);
// Marker for the preamble
preamble = "// fid-umd " + config.exportConfig() + "\n";
if (config.jslint) {
preamble += "/*jslint this*/\n";
}
preamble += this.jslintGlobalVariables(config);
if (config.global) {
preamble += "(function (name, root, factoryForGlobal) {\n";
} else {
preamble += "(function (name, root, factory) {\n";
}
if (config.jslint) {
preamble += " \"use strict\";\n";
}
if (config.global) {
// This calls factoryWithoutContext in the global scope
preamble += this.createPreambleGlobalScope(config);
}
preamble += this.writeNeededFunctions(config);
if (config.debug) {
preamble += " console.log(\"Attempting to define \" + name);\n";
}
// Add the various loaders
for (i = 0; i < moduleSystems.length; i += 1) {
if (i === 0) {
preamble += " ";
} else {
preamble += " else ";
}
code = moduleSystems[i].condition(config);
if (code) {
preamble += "if (" + code + ") ";
}
preamble += "{\n";
if (config.debug) {
preamble += " console.log(\"" + moduleSystems[i].name + " detected\");\n";
}
preamble += " ";
preamble += moduleSystems[i].loader();
preamble += "\n";
if (config.debug) {
preamble += " console.log(\"" + moduleSystems[i].name + " success\");\n";
}
preamble += " }";
}
// Finish the preamble
dependencies = config.dependsProperty("name").join(", ");
preamble += "\n";
preamble += "}(" + JSON.stringify(config.name) + ", this, function (" + dependencies + ") {\n";
if (config.jslint) {
preamble += " \"use strict\";\n";
}
preamble += " // fid-umd end\n";
return preamble;
};
/**
* Defines the factory() function when we want to execute the
* factory in global scope
*
* @param {!Config} config Determines dependencies
* @return {string} Preamble
*/
FidUmd.prototype.createPreambleGlobalScope = function (config) {
var dependencies, preamble;
dependencies = config.dependsProperty("name").join(", ");
preamble = " function factory(" + dependencies + ") { return factoryForGlobal.call(root";
if (dependencies) {
preamble += ", " + dependencies;
}
preamble += "); };\n";
return preamble;
};
/**
* Detect an existing config or create a new default config
*
* @param {string} code
* @param {Function} ConfigConstructor
* @return {Object} Short version of {@link FidUmd~config} object
*/
FidUmd.prototype.detectConfig = function (code, ConfigConstructor) {
var config, matches, parsedConfig;
matches = code.match(/\/\/ fid-umd (\{.*\})/);
config = new ConfigConstructor();
if (matches) {
try {
parsedConfig = JSON.parse(matches[1]);
config.loadConfig(parsedConfig);
} catch (e) {
throw new Error("Invalid JSON: " + matches[1]);
}
}
return config;
};
/**
* To avoid warnings with jslint, write a "globals" declaration
* at the top of the UMD
*
* @param {Config} config
* @return {string}
*/
FidUmd.prototype.jslintGlobalVariables = function (config) {
var globals, name;
globals = [];
for (name in config.globalVariables) {
if (config.globalVariables.hasOwnProperty(name)) {
globals.push(name);
}
}
if (!config.jslint || !globals.length) {
return "";
}
return "/* global " + globals.join(", ") + " */\n";
};
/**
* Split code into chunks
*
* Look for the first marker.
* If not found, return [ code, '' ]
* If found, look for the second marker after the first
* If not found, return [ code-before-first, code-after-first ]
* If found, return [code-before-first, code-after-second ]
*
* @param {string} code
* @param {!RegExp} startMarker
* @param {!RegExp} endMarker
* @return {Array.<string>} Code before and after markers
*/
FidUmd.prototype.splitCode = function (code, startMarker, endMarker) {
var match, result;
result = [
code,
""
];
match = code.match(startMarker);
if (!match) {
// Did not match
return result;
}
result[0] = code.substr(0, match.index);
result[1] = code.substr(match.index + match[0].length);
// Now look for the end marker
match = result[1].match(endMarker);
if (match) {
// Limit the "after" portion to everything after the marker
result[1] = result[1].substr(match.index + match[0].length);
}
return result;
};
/**
* Update the UMD code in some JavaScript
*
* @param {string} oldCode
* @return {string} Updated code with new preamble and postamble
*/
FidUmd.prototype.update = function (oldCode) {
var config, newCode;
if (!oldCode) {
oldCode = "";
}
oldCode = oldCode.toString();
config = this.detectConfig(oldCode, Config);
newCode = oldCode;
newCode = this.updatePreamble(newCode, config);
newCode = this.updatePostamble(newCode);
return newCode;
};
/**
* Update the postamble
*
* @param {string} inCode Code to update
* @param {!Config} config
* @return {string} Updated code
*/
FidUmd.prototype.updatePostamble = function (inCode) {
var outCode, outCodePieces;
outCodePieces = this.splitCode(inCode, /^[\t ]*\/\/ fid-umd post\n?/m, /[\t ]*\/\/ fid-umd post-end\n?/m);
outCode = outCodePieces[0];
if (outCode.substr(-1) !== "\n") {
outCode += "\n";
}
outCode += this.createPostamble() + outCodePieces[1];
return outCode;
};
/**
* Update the preamble
*
* @param {string} inCode Code to update
* @param {!Config} config
* @return {string} Updated code
*/
FidUmd.prototype.updatePreamble = function (inCode, config) {
var outCode, outCodePieces;
outCodePieces = this.splitCode(inCode, /^[ \t]*\/\/ fid-umd \{.*\n?/m, /^[ \t]*\/\/ fid-umd end\n?/m);
// Force the preamble to be at the beginning
if (outCodePieces[1] === "") {
outCodePieces[1] = outCodePieces[0];
outCodePieces[0] = "";
}
outCode = outCodePieces[0] + this.createPreamble(config) + outCodePieces[1];
return outCode;
};
/**
* Write out the code for necessary functions.
*
* @param {!Config} config
* @return {string}
*/
FidUmd.prototype.writeNeededFunctions = function (config) {
var result;
result = "";
if (config.functionsNeeded.isObject) {
if (config.jslint) {
result += " function isObject(x) {\n return typeof x === \"object\";\n }\n";
} else {
result += " function isObject(x) { return typeof x === \"object\"; }\n";
}
}
return result;
};
module.exports = FidUmd;