uupaa.nodemodule.js
Version:
Node Module Utility.
480 lines (404 loc) • 19.3 kB
JavaScript
(function(global) {
;
// --- dependency modules ----------------------------------
var fs = require("fs");
// --- define / local variables ----------------------------
//var _runOnNode = "process" in global;
//var _runOnWorker = "WorkerLocation" in global;
//var _runOnBrowser = "document" in global;
// --- class / interfaces ----------------------------------
function NodeModule() {
}
//{@dev
NodeModule["repository"] = "https://github.com/uupaa/NodeModule.js";
//}@dev
// --- convenient ---
NodeModule["files"] = NodeModule_files; // NodeModule.files(param:Object = null):Object
// --- raw ---
NodeModule["loadPackageJSON"] = NodeModule_loadPackageJSON; // NodeModule.loadPackageJSON(file:PathString):Object/null
NodeModule["validatePackageJSON"] = NodeModule_validatePackageJSON; // NodeModule.validatePackageJSON(file:PathString, pkg:Object):TypeError/null
NodeModule["collectPackageData"] = NodeModule_collectPackageData; // NodeModule.collectPackageData(param:Object):Object
NodeModule["removeDevToolModules"] = NodeModule_removeDevToolModules; // NodeModule.removeDevToolModules(pakcageData:Object):Object
NodeModule["resolveDependencyTree"] = NodeModule_resolveDependencyTree; // NodeModule.resolveDependencyTree(pakcageData:Object):Object
NodeModule["createSortedFileList"] = NodeModule_createSortedFileList; // NodeModule.createSortedFileList(pakcageData:Object, tree:Object):Object
NodeModule["sortModuleListByDependencyOrder"] = NodeModule_sortModuleListByDependencyOrder; // NodeModule.sortModuleListByDependencyOrder(list:ModuleNameStringArray, tree:Object):ModuleNameStringArray
// --- utility ---
NodeModule["uniqueArray"] = NodeModule_uniqueArray; // NodeModule.uniqueArray(source:Array):Object
NodeModule["verbose"] = true;
// --- implements ------------------------------------------
function NodeModule_files(param) { // @arg Object - { dir, ignore, develop, packageFiles }
// @param.dir PathString = "" - search base directory.
// @param.ignore ModuleNameArray = [] - ignore module names. ["uupaa.watch.js", ...]
// @param.develop Boolean = false - develop mode.
// @param.packageFiles Object = null - package.json files for UnitTest.
// @ret Object - { all, node, worker, browser, label }
// @return.all RelativeFilePathArray - all
// @return.node RelativeFilePathArray - for Node.js
// @return.worker RelativeFilePathArray - for WebWorker
// @return.browser RelativeFilePathArray - for Browser
// @return.label PathStringArray - label.
param = param || {};
var data = NodeModule_collectPackageData(param); // { json, tree, list }
NodeModule_removeDevToolModules(data);
var tree = NodeModule_resolveDependencyTree(data);
var files = NodeModule_createSortedFileList(data, param["develop"] ? tree["develop"]
: tree["release"]);
//console.log("\u001b[31m" + "data: " + JSON.stringify(data, null, 2));
//console.log("\u001b[33m" + "tree: " + JSON.stringify(tree, null, 2));
//console.log("\u001b[32m" + "files: " + JSON.stringify(files, null, 2) + "\u001b[0m");
return files; // { all, node, worker, browser }
}
function NodeModule_loadPackageJSON(file, // @arg PathString - package.json file path.
packageFiles) { // @arg Object = null - { package.json: JSON, ... }
// @ret Object|null - package.json object
if (packageFiles) {
if (file in packageFiles) { // is cached package.json?
return packageFiles[file];
}
}
if ( !fs["existsSync"](file) ) {
return null;
}
return JSON.parse( fs["readFileSync"](file) );
}
function NodeModule_validatePackageJSON(file, // @arg PathString - package.json file path.
pkg) { // @arg Object - package.json object.
// @ret TypeError|null - null is success
// package.json:
// {
// "name": "package.name",
// "x-build": {
// "source": [],
// "target": [],
// "labels": [],
// "module": {
// "develop": ["uupaa.valid.js"],
// "release": []
// }
// },
// "dependencies": {
// },
// "devDependencies": {
// "uupaa.valid.js"
// }
// }
var name = pkg["name"];
if ( !name || typeof name !== "string") {
return _formatError(file, "name");
}
var build = pkg["x-build"] || pkg["build"];
if ( !build || !_isObject(build) ) {
return _formatError(file, "build");
}
var source = build["source"];
if ( !source || !Array.isArray(source) ) {
return _formatError(file, "x-build.source");
}
var target = build["target"];
if ( !target || !Array.isArray(target) ) {
return _formatError(file, "x-build.target");
}
var label = build["label"] || build["labels"];
if ( !label || !Array.isArray(label) ) {
_formatWarning(file, "x-build.label");
}
if ("labels" in build) {
console.warn("Warning: " + file + ", Should rename of x-build.labels to x-build.label");
}
var module = build["module"]; // { develop: [], release: [] }
if ( !module || !_isObject(module) ) {
return _formatError(file, "x-build.module");
}
var module_develop = module["develop"];
if ( !module_develop || !Array.isArray(module_develop) ) {
return _formatError(file, "x-build.module.develop");
}
var module_release = module["release"];
if ( !module_release || !Array.isArray(module_release) ) {
return _formatError(file, "x-build.module.release");
}
var dependencies = pkg["dependencies"];
if ( !dependencies || !_isObject(dependencies) ) {
return _formatError(file, "dependencies");
}
var devDependencies = pkg["devDependencies"];
if ( !devDependencies || !_isObject(devDependencies) ) {
return _formatError(file, "devDependencies");
}
var packageModules = [].concat(dependencies, devDependencies);
var has = module_develop.every(function(devlop) {
return packageModules.indexOf(devlop);
});
if (!has) {
return _formatError(file, "x-build.module.develop");
}
has = module_release.every(function(devlop) {
return packageModules.indexOf(devlop);
});
if (!has) {
return _formatError(file, "x-build.module.release");
}
return null;
function _isObject(source) {
return source["constructor"] === ({})["constructor"];
}
function _formatError(file, hint) {
return new TypeError("Error: " + file + ", does not conform to the format of WebModule. hint: " + hint);
}
function _formatWarning(file, hint) {
console.warn("Warning: " + file + ", " + hint + " not found");
}
}
function NodeModule_collectPackageData(param) { // @arg Object - { dir, ignore, develop, packageFiles }
// @param.dir PathString = "" - search base directory.
// @param.ignore ModuleNameArray = [] - ["uupaa.watch.js", ...]
// @param.develop Boolean = false - true is search develop information.
// @param.packageFiles Object = null - package.json files for UnitTest.
// @ret Object - PackageObject: package.json data. { json, tree, list }
var dir = param["dir"] || "";
var ignore = param["ignore"] || [];
var develop = param["develop"] || false;
var packageFiles = param["packageFiles"] || null;
var json = {};
var tree = {};
var list = [];
var rootDirLength = dir.length;
_collect(dir, develop, json, tree);
return {
"json": json,
"tree": tree,
"list": _removePhantomModule(list, json)
};
function _collect(dir, develop, json, tree) {
var file = dir + "package.json";
var pkg = NodeModule_loadPackageJSON(file, packageFiles);
if (!pkg) {
return;
}
var formatError = NodeModule_validatePackageJSON(file, pkg);
if (formatError) {
throw formatError;
}
_collectJSON(json, develop, pkg, dir, rootDirLength);
if (develop) {
Object.keys(pkg["devDependencies"]).forEach(_next);
}
Object.keys(pkg["dependencies"]).forEach(_next);
function _next(name) {
if (ignore.indexOf(name) < 0) {
if (list.indexOf(name) < 0) {
list.push(name);
}
tree[name] = {};
_collect(dir + "node_modules/" + name + "/", develop, json, tree[name]);
}
}
}
}
function _collectJSON(json, // @arg Object
develop, // @arg Boolean
pkg, // @arg JSON - package.json object.
dir, // @arg PathString
rootDirLength) { // @arg Integer - dir.length
// --- pickup package.json data ---
var name = pkg["name"];
var build = pkg["x-build"] || pkg["build"];
var source = build["source"] ||
build["files"]; // [DEPRECATED]
var target = build["target"].join(" ");
var module = build["module"]; // { develop: [], release: [] }
var label = build["label"] || build["labels"] || [];
var developModule = module["develop"];
var releaseModule = module["release"];
var dependencies = pkg["dependencies"];
var devDependencies = pkg["devDependencies"];
// --- expand module file path ---
// file = (dir + file).slice(rootDirectoryLength);
source = source.map(function(file) {
return (dir + file).slice(rootDirLength);
});
// --- stock module build setting ---
if ( !(name in json) || // found new module -> stock
json[name]["source"][0].length > source[0].length ) { // more short -> overwrite
json[name] = {
"source": source,
"target": target,
"label": label,
"developModule": developModule,
"releaseModule": releaseModule,
"dependencies": dependencies,
"devDependencies": devDependencies
};
if (develop) { // --- merge module ---
// developModule = unique(merge(developModule, releaseModule))
if (developModule.length && releaseModule.length) {
json[name]["developModule"] = NodeModule_uniqueArray( developModule.concat(releaseModule) ).unique;
}
}
}
}
function _removePhantomModule(moduleList, // @arg ModuleNameArray - [name, ...]
json) { // @arg Object - packageData
// @ret ModuleNameArray
// @desc Remove a module that does not exist.
return moduleList.reduce(function(result, module) {
if (module in json) {
result.push(module);
return result;
}
return result;
}, []);
}
function NodeModule_removeDevToolModules(packageData) { // @arg Object - NodeModule.collectPackageData() result value
// @ret Object - packageData
packageData["list"] = packageData["list"].reduce(function(result, name) {
if (!packageData["json"][name]["target"].length) {
return result;
}
result.push(name);
return result;
}, []);
return packageData;
}
function NodeModule_resolveDependencyTree(packageData) { // @arg Object - NodeModule.collectPackageData() result value
// @ret Object - { develop, relase }
// @packageData.develop Object - develop build dependency object tree.
// @packageData.release Object - release build dependency object tree.
// @desc resolve module tree by dependency order
var json = packageData["json"];
var tree = packageData["tree"];
var dependencyData = { "develop": {}, "release": {} };
["develop", "release"].forEach(function(type) {
for (var name in tree) {
dependencyData[type][name] = {};
_next(name, tree[name], dependencyData[type][name], type);
}
});
return dependencyData;
function _next(name, tree, dependencyData, type) {
if (name in json) {
var buildModuleNames = json[name][type + "Module"]; // developModule, releaseModule
if (buildModuleNames.length) {
buildModuleNames.forEach(function(name) {
if (name in tree) {
dependencyData[name] = {};
_next(name, tree[name], dependencyData[name], type);
}
});
}
}
}
}
function NodeModule_createSortedFileList(packageData, // @arg Object - NodeModule.data() result value
tree) { // @arg Object - NodeModule.dependency() result value.
// @ret Object - { all, node, worker, browser, label }
// @return.all PathStringArray - all files.
// @return.node PathStringArray - Node.js files.
// @return.worker PathStringArray - Worker files.
// @return.browser PathStringArray - Browser files.
// @return.label PathStringArray - label.
var json = packageData["json"];
var list = NodeModule_sortModuleListByDependencyOrder(packageData["list"], tree);
var all = [];
var node = [];
var worker = [];
var browser = [];
var label = [];
list.forEach(function(name) {
if (name in json) {
var target = json[name]["target"]; // ["all", "browser", "worker", "node"]
var toNode = /(all|node)/i.test(target);
var toWorker = /(all|worker)/i.test(target);
var toBrowser = /(all|browser)/i.test(target);
Array.prototype.push.apply(label, json[name]["label"]);
json[name]["source"].forEach(function(file) {
if (all.indexOf(file) < 0) {
all.push(file);
}
if (toNode && node.indexOf(file) < 0) {
node.push(file);
}
if (toWorker && worker.indexOf(file) < 0) {
worker.push(file);
}
if (toBrowser && browser.indexOf(file) < 0) {
browser.push(file);
}
});
}
});
label = label.reduce(function(result, label) {
if (result.indexOf(label) < 0) {
result.push(label);
}
return result;
}, []);
return {
"all": all,
"node": node,
"worker": worker,
"browser": browser,
"label": label
};
}
function NodeModule_sortModuleListByDependencyOrder(list, // @arg ModuleNameStringArray - [moduleName, ...]
tree) { // @arg Object - module dependency tree. { moduleName: { subModuleName, ... }, ... }
// @ret ModuleNameStringArray - sorted new array.
var verbose = NodeModule["verbose"] || false;
if (verbose) { console.log("list: " + JSON.stringify(list, null, 2)); }
if (verbose) { console.log("tree: " + JSON.stringify(tree, null, 2)); }
// https://gist.github.com/uupaa/ad495d35af90a4859e2c
var pathList = _makePathList([], "", tree);
for (var i = 0, iz = pathList.length; i < iz; ++i) {
var path = pathList[i];
var modules = path.split("/").slice(1); // "/a/b" -> ["a", "b"]
for (var j = 0, jz = modules.length - 1; j < jz; ++j) {
var moduleA = modules[j];
var moduleB = modules[j + 1];
var posA = list.indexOf(moduleA);
var posB = list.indexOf(moduleB);
if (posA >= 0 && posB >= 0) {
if (posA < posB) { // moduleA need moduleB.
// move the mobuleB to before mobuleA.
list.splice(posB, 1); // Remove the moduleB,
list.splice(posA, 0, moduleB); // and injecting it to before moduleA.
}
}
}
}
return list;
}
function _makePathList(result, dir, subtree) {
for (var key in subtree) {
var path = dir + "/" + key;
result.push(path);
_makePathList(result, path, subtree[key]);
}
return result;
}
function NodeModule_uniqueArray(source) { // @arg Array
// @ret Object - { unique:Array, dup:Array }
var unique = [], dup = [], i = 0, iz = source.length;
for (; i < iz; ++i) {
if (unique.indexOf(source[i]) >= 0) {
dup.push(source[i]);
} else {
unique.push(source[i]);
}
}
return { unique: unique, dup: dup };
}
// --- validate / assertions -------------------------------
//{@dev
//function $valid(val, fn, hint) { if (global["Valid"]) { global["Valid"](val, fn, hint); } }
//function $type(obj, type) { return global["Valid"] ? global["Valid"].type(obj, type) : true; }
//function $keys(obj, str) { return global["Valid"] ? global["Valid"].keys(obj, str) : true; }
//function $some(val, str, ignore) { return global["Valid"] ? global["Valid"].some(val, str, ignore) : true; }
//function $args(fn, args) { if (global["Valid"]) { global["Valid"].args(fn, args); } }
//}@dev
// --- exports ---------------------------------------------
if ("process" in global) {
module["exports"] = NodeModule;
}
global["NodeModule" in global ? "NodeModule_" : "NodeModule"] = NodeModule; // switch module. http://git.io/Minify
})((this || 0).self || global); // WebModule idiom. http://git.io/WebModule