var fs = require('fs'); var pathLib = require('path'); var through = require('through'); var nativeNodeModules = ["fs", "path", "require", "exports", "module"]; module.exports = function(mains, opts) { if (!Array.isArray(mains)) mains = [mains].filter(Boolean); opts = opts || Object.create(null); opts.modules = Object.create(null); // {id, path, contents, deps} var cache = opts.cache; if (cache) cache.files = cache.files || Object.create(null); if (!opts.ignore) opts.ignore = []; else opts.ignore = opts.ignore.reduce(function(map, ignore) { map[ignore] = true; return map; }, {}); if (opts.debug) opts.transforms = [wrapUMD, debugSrc]; if (!opts.transforms) opts.transforms = []; opts.transforms.push(removeLicenceComments, wrapUMD); if (opts.pathConfig) { opts.paths = opts.paths || opts.pathConfig.paths; opts.root = opts.root || opts.pathConfig.root; opts.packages = opts.packages || opts.pathConfig.packages; opts.pathMap = opts.pathMap || opts.pathConfig.pathMap; } var root = opts.root || ""; var paths = opts.paths || {}; var pathMap = opts.pathMap; opts.packages && opts.packages.forEach(function(pkg) { paths[pkg.name] = pkg; }); function idToPath(mod, cb) { if (!mod.id && mod.path) return cb(null, mod.path); var id = mod.id; if (id.indexOf("!") != -1) { var chunks = id.split("!"); id = chunks[1]; mod.loaderModule = chunks[0]; } id = resolveModuleId(id, paths); if (pathMap) id = resolveModulePath(id, pathMap); if (!mod.loaderModule && !/\.js$/.test(id)) id += ".js"; cb(null, id); } function readModule(mod, cb) { if (typeof mod == "string") mod = {id: mod}; var id = mod.id; if (opts.modules[id]) { return cb(null, null); } if (opts.ignore[id]) { ignoreDep(mod); return cb(); } opts.modules[id] = mod; if (mod.source && mod.literal) { return cb(null, mod); } idToPath(mod, function(err, path) { var filepath = absolutePath(root, path); mod.path = path; mod.file = filepath; if (cache && cache.files[mod.file] != undefined) { return setModuleSource(mod, cache.files[mod.file], cb); } fs.readFile(filepath, 'utf8', function (err, src) { if (err) { if (err.code == 'ENOENT' || err.code == "ENOTDIR") { ignoreDep(mod); cb(); return; } if (err.code == 'EMFILE') { opts.modules[id] = null; wait(err, mod, cb); return; } return output.emit('error', err); } setModuleSource(mod, src, cb); }); }); } function setModuleSource(mod, src, cb) { if (cache) cache.files[mod.file] = src; mod.source = src; // todo: support loaders other than text? if (!mod.noRequire && !mod.literal && !mod.loaderModule && !mod.noDeps) { mod.deps = getDeps(src, mod.id); // console.log(mod.id, mod.deps) mod.deps.forEach(function(dep) { addToQueue(dep, mod); }); } cb(null, mod); } function ignoreDep(mod) { var parent = mod.parent || {}; if (nativeNodeModules[mod.id] === -1) { console.log("file '" + mod.file + "' doesn't exisit"); console.log("ignoring dependency '" + mod.id + "' of '" + parent.id + "'"); } if (!parent.ignoredDeps) parent.ignoredDeps = []; parent.ignoredDeps.push(mod); } var output = through(); var pending = []; var active = 0; var maxActive = 100; var waitT = 0, timer; function addToQueue(id, parent) { var mod = typeof id == "string" ? {id: id, parent: parent} : id; pending.push(mod); } function wait(err, mod) { active--; pending.push(mod); if (active > 0) { if (active > 2 && active < maxActive) maxActive = active; } else if (waitT < 5000) { if (!timer) { waitT = 1.5 * waitT + 1 +0.001 * waitT * waitT; timer = setTimeout(function() { timer = null; takeFromQueue(); if (active) waitT = 0; }, waitT); } } else { output.emit('error', err); } } function takeFromQueue() { while (active < maxActive) { var mod = pending.pop(); if (!mod) break; active++; readModule(mod, onModuleReady); } } function onModuleReady(err, module) { active--; // console.log("pending", pending) if (module && module.source != undefined) { applyTransforms(module); output.queue(module); } else if (module) { module.parent = null; console.dir(module); } if (pending.length || active) takeFromQueue(); else end(); } function end() { opts._createDepMap && fs.writeFileSync("dep_map.js", JSON.stringify(opts.modules, function(key, val) { if (key == "source") return val && val[0]; if (key == "parent") return val && val.id; return val; }, 4)); process.nextTick(function() { output.emit("end"); }); } function applyTransforms(module) { if (!module.source || module.literal) return; // console.log(module) var transforms = module.transforms || opts.transforms; transforms.forEach(function(transform) { transform(module); }); } mains.forEach(function(dep) { addToQueue(dep, {id: "#root"}); }); takeFromQueue(); return output; }; function normalizeModule(parentId, moduleName) { // normalize plugin requires if (moduleName.indexOf("!") !== -1) { var chunks = moduleName.split("!"); return normalizeModule(parentId, chunks[0]) + "!" + normalizeModule(parentId, chunks[1]); } // normalize relative requires if (moduleName.charAt(0) == ".") { var base = parentId.split("/").slice(0, -1).join("/"); moduleName = (base || parentId) + "/" + moduleName; while (moduleName.indexOf(".") !== -1 && previous != moduleName) { var previous = moduleName; moduleName = moduleName.replace(/\/\.\//, "/").replace(/[^\/]+\/\.\.\//, ""); } } return moduleName; } function getSubmodules(src, name) { var m = src.replace(/^\s*\/\/.+|^\s*\/\*[\s\S]*?\*\//gm, "") .match(/require\(\[([^\]]+)\]/gm); if (!m) return []; var deps = []; m.forEach(function(dep) { var re = /["']([^"']+?)["']/g; var m; while ((m = re.exec(dep))) { deps.push(normalizeModule(name, m[1])); } }); // console.log(deps); return deps; } function getReqDeps(src, name) { var m = src.replace(/^\s*\/\/.+|^\s*\/\*[\s\S]*?\*\//gm, "") .match(/require\s*\(\s*(["'][^"'\n\r]+["'])\s*\)/gm); if (!m) return []; return m.map(function(r) { r = r.match(/["']([^"']+?)["']/)[1]; r = normalizeModule(name, r); return r; }); } function getAmdDeps(src, name) { var m = src.match(/define\(\s*\[[^\]]+\]/gm); if (!m) return []; var deps = []; m.forEach(function(dep) { var re = /["']([^"']+?)["']/g; var m; while ((m = re.exec(dep))) { deps.push(normalizeModule(name, m[1])); } }); // console.log(deps); return deps; } function getDeps(src, name) { return getReqDeps(src, name).concat(getAmdDeps(src, name)); } function resolveModuleId(id, paths) { var testPath = id, tail = ""; while (testPath) { var alias = paths[testPath]; if (typeof alias == "string") { return alias + tail; } else if (alias) { return alias.location.replace(/\/*$/, "/") + (tail || alias.main || alias.name); } else if (alias === false) { return ""; } var i = testPath.lastIndexOf("/"); if (i === -1) break; tail = testPath.substr(i) + tail; testPath = testPath.slice(0, i); } return id; } function resolveModulePath(id, pathMap) { var testPath = id, tail = ""; if (testPath[0] != "/") testPath = "/" + testPath; while (testPath) { if (pathMap[testPath]) { return pathMap[testPath] + tail; } var i = testPath.lastIndexOf("/"); if (i === -1) break; tail = testPath.substr(i) + tail; testPath = testPath.slice(0, i); } return id; } function absolutePath(root, relative) { // console.log(root, relative) if (relative[0] == "/" || relative[1] == ":") return relative; return pathLib.join(root, relative); } // src transformations function removeUseStrict(module) { module.source = module.source.replace(/['"]use strict['"];/g, ""); } function removeLicenceComments(module) { if (/\.(js|jsx|css|less)/.test(module.path)) module.source = module.source.replace(/(?:(;)|\n|^)\s*\/\*[\d\D]*?\*\/|(\n|^)\s*\/\/.*/g, "$1"); } function removeLicenceCommentsKeepLines(module) { if (/\.(js|jsx|css|less)/.test(module.path)) { module.source = module.source.replace(/(?:(;)|\n|^)\s*\/\*[\d\D]*?\*\/|\n\s*\/\/.*/g, function(cm, start) { return (start||"") + cm.replace(/.+/g, ""); }); } } function wrapUMD(module) { if (module.loaderModule || module.noRequire) return; var firstDefineCall = module.source.match(/define\(\s*[^)]*/); if (firstDefineCall) { // check if it is a normal define or some crazy umd trick if (/define\(\s*function\s*\(/.test(firstDefineCall[0])) return; if (/define\(\s*\[[^\]]*\],\s*function\(/.test(firstDefineCall[0])) return; } console.log("wrapping module " + module.id); module.source = 'define(function(require, exports, module) {\n' + 'var $build_deps$ = {require: require, exports: exports, module: module};\n' + 'exports = undefined; module = undefined;\n' + 'function define(name, deps, m) {\n' + ' if (typeof name == "function") {\n' + ' m = name; deps = ["require", "exports", "module"]; name = $build_deps$.module.id\n' + ' }\n' + ' if (typeof name !== "string") {\n' + ' m = deps; deps = name; name = $build_deps$.module.id\n' + ' }\n' + ' if (!m) {\n' + ' m = deps; deps = [];\n' + ' }\n' + ' var ret = typeof m == "function" ?\n' + ' m.apply($build_deps$.module, deps.map(function(n){return $build_deps$[n] || require(n)})) : m\n' + ' if (ret != undefined) $build_deps$.module.exports = ret;\n' + ' if (name != $build_deps$.module.id && $build_deps$.module.define) {\n' + ' $build_deps$.module.define(name, [], function() { return $build_deps$.module.exports });\n' + ' }\n' + '}\n' + 'define.amd = true;' + module.source + '});'; } function debugSrc(module) { if (module.loaderModule) return; var url = "http://localhost:8181/" + module.id; module.source = "try{eval(" + quote(module.source + "\n//@ sourceURL=" + url) + ");}catch(e){throw e.message + url}"; } function quote(str) { return "'" + str.replace(/[\\']/g, "\\$&").replace(/\n/g, "\\n") + "'"; } module.exports.resolveModulePath = resolveModulePath;