amd-optimize
Version:
An AMD (i.e. RequireJS) optimizer that's stream-friendly. Made for gulp. (WIP)
236 lines (222 loc) • 8.3 kB
JavaScript
(function() {
var Module, VinylFile, _, async, fs, parse, path, traceModule, util;
_ = require("lodash");
fs = require("fs");
path = require("path");
async = require("async");
VinylFile = require("vinyl");
parse = require("./parse");
util = require("./util");
Module = (function() {
function Module(name, file1, deps1) {
this.name = name;
this.file = file1;
this.deps = deps1 != null ? deps1 : [];
this.name = util.fixModuleName(this.name);
this.isShallow = false;
this.isShimmed = false;
this.isAnonymous = false;
this.isInline = false;
this.hasDefine = false;
this.astNodes = [];
}
return Module;
})();
module.exports = traceModule = function(startModuleName, config, allModules, fileLoader, callback) {
var emitModule, foundModuleNames, jsonFiles, resolveInlinedModule, resolveModule, resolveModuleFileName, resolveModuleName, resolveModules, textFiles;
if (allModules == null) {
allModules = [];
}
foundModuleNames = [];
textFiles = {};
jsonFiles = {};
resolveModuleName = function(moduleName, relativeTo) {
var eligiblePath, isJson, isText, relativeToFileName, slashIdx;
if (relativeTo == null) {
relativeTo = "";
}
isText = moduleName.indexOf('text!') !== -1;
if (isText) {
moduleName = moduleName.replace('text!', '');
}
isJson = moduleName.indexOf('json!') !== -1;
if (isJson) {
moduleName = moduleName.replace('json!', '');
}
if (config.paths && !config.paths[moduleName]) {
slashIdx = moduleName.indexOf("/");
if (slashIdx > 0) {
eligiblePath = config.paths[moduleName.substr(0, slashIdx)];
if (eligiblePath) {
moduleName = eligiblePath + moduleName.substr(slashIdx);
}
}
}
relativeToFileName = resolveModuleFileName(relativeTo);
if (moduleName[0] === ".") {
moduleName = util.fixModuleName(path.join(path.dirname(relativeToFileName), moduleName));
}
if (config.map && config.map[relativeTo] && config.map[relativeTo][moduleName]) {
moduleName = config.map[relativeTo][moduleName];
}
if (isText) {
textFiles[moduleName] = true;
}
if (isJson) {
jsonFiles[moduleName] = true;
}
return moduleName;
};
resolveModuleFileName = function(moduleName) {
if (config.paths && config.paths[moduleName]) {
moduleName = config.paths[moduleName];
}
if (/!|^exports$|^require$|^module$|^empty:/.test(moduleName)) {
} else {
return moduleName;
}
};
resolveModules = function(moduleNames, callback) {
async.mapSeries(moduleNames, resolveModule, callback);
};
resolveInlinedModule = function(moduleName, deps, astNode, vinylFile, callback) {
async.waterfall([
function(callback) {
return resolveModules(deps, callback);
}, function(modules, callback) {
var module;
module = new Module(moduleName, vinylFile, _.compact(modules));
module.hasDefine = true;
module.isInline = true;
module.astNodes.push(astNode);
emitModule(module);
return callback();
}
], callback);
};
resolveModule = function(moduleName, callback) {
var fileName, isJsonFile, isTextFile, module;
module = _.detect(allModules, {
name: moduleName
});
if (module) {
callback(null, module);
return;
}
fileName = resolveModuleFileName(moduleName);
if (!fileName) {
module = new Module(moduleName);
module.isShallow = true;
callback(null, emitModule(module));
return;
}
if (_.contains(foundModuleNames, moduleName)) {
callback(new Error("Circular dependency detected. Module '" + moduleName + "' has been processed before."));
return;
} else {
foundModuleNames.push(moduleName);
}
module = null;
isTextFile = !!textFiles[moduleName];
isJsonFile = !!jsonFiles[moduleName];
async.waterfall([
function(callback) {
return fileLoader(fileName, callback, isTextFile || isJsonFile);
}, function(file, callback) {
if (arguments.length === 1) {
callback = file;
file = null;
}
if (file) {
return callback(null, file);
} else {
return callback(new Error("No file for module '" + moduleName + "' found."));
}
}, function(file, callback) {
file.stringContents = file.contents.toString("utf8");
if (isTextFile) {
file.stringContents = 'define(function(){ return ' + JSON.stringify(file.stringContents) + '; });';
}
if (isJsonFile) {
file.stringContents = 'define(function(){ return JSON.parse(' + JSON.stringify(file.stringContents) + '); });';
}
module = new Module(moduleName, file);
return callback(null, file);
}, parse.bind(null, config), function(file, definitions, callback) {
if (_.filter(definitions, function(def) {
var ref;
return def.method === "define" && def.moduleName === void 0 && (0 < (ref = def.argumentsLength) && ref < 3);
}).length > 1) {
callback(new Error("A module must not have more than one anonymous 'define' calls."));
return;
}
module.hasDefine = _.any(definitions, function(def) {
return def.method === "define" && (def.moduleName === void 0 || def.moduleName === moduleName);
});
return async.mapSeries(definitions, function(def, callback) {
def.deps = def.deps.map(function(depName) {
var ref;
return resolveModuleName(depName, (ref = def.moduleName) != null ? ref : moduleName);
});
if (def.method === "define" && def.moduleName !== void 0 && def.moduleName !== moduleName) {
async.waterfall([
function(callback) {
return resolveInlinedModule(def.moduleName, def.deps, def.node, file, callback);
}, function(callback) {
return callback(null, []);
}
], callback);
} else {
module.astNodes.push(def.node);
resolveModules(def.deps, callback);
}
}, callback);
}, function(unflatModules, callback) {
return callback(null, _.compact(_.flatten(unflatModules)));
}, function(depModules, callback) {
var ref;
(ref = module.deps).push.apply(ref, depModules);
module.isAnonymous = true;
async.waterfall([
function(callback) {
var additionalDepNames, shim;
additionalDepNames = null;
if (config.shim && (shim = config.shim[module.name])) {
if (module.hasDefine) {
console.log("[warn]", "Module '" + module.name + "' is shimmed even though it has a proper define.");
}
module.isShimmed = true;
if (shim.exports) {
module.exports = shim.exports;
}
if (_.isArray(shim)) {
additionalDepNames = shim;
} else if (shim.deps) {
additionalDepNames = shim.deps;
}
}
if (additionalDepNames) {
return resolveModules(additionalDepNames, callback);
} else {
return callback(null, []);
}
}, function(depModules, callback) {
var ref1;
(ref1 = module.deps).push.apply(ref1, depModules);
return callback(null, emitModule(module));
}
], callback);
}
], callback);
};
emitModule = function(module) {
if (!_.any(allModules, {
name: module.name
})) {
allModules.push(module);
}
return module;
};
resolveModule(startModuleName, callback);
};
}).call(this);