mirror of https://github.com/webpack/webpack.git
378 lines
10 KiB
JavaScript
378 lines
10 KiB
JavaScript
/*
|
|
MIT License http://www.opensource.org/licenses/mit-license.php
|
|
Author Tobias Koppers @sokra
|
|
*/
|
|
var parse = require("./parse");
|
|
var resolve = require("./resolve");
|
|
var fs = require("fs");
|
|
var path = require("path");
|
|
|
|
/**
|
|
* context: current directory
|
|
* mainModule: the entrance module
|
|
* options:
|
|
* callback: function(err, result)
|
|
*/
|
|
module.exports = function buildDeps(context, mainModule, options, callback) {
|
|
if(!callback) {
|
|
callback = options;
|
|
options = {};
|
|
}
|
|
if(!options) options = {};
|
|
|
|
var depTree = {
|
|
warnings: [],
|
|
modules: {},
|
|
modulesById: {},
|
|
chunks: {},
|
|
nextModuleId: 0,
|
|
nextChunkId: 0,
|
|
chunkModules: {} // used by checkObsolete
|
|
}
|
|
var mainModuleId;
|
|
addModule(depTree, context, mainModule, options, {type: "main"}, function(err, id) {
|
|
if(err) {
|
|
callback(err);
|
|
return;
|
|
}
|
|
mainModuleId = id;
|
|
buildTree();
|
|
});
|
|
function buildTree() {
|
|
addChunk(depTree, depTree.modulesById[mainModuleId], options);
|
|
for(var chunkId in depTree.chunks) {
|
|
removeParentsModules(depTree, depTree.chunks[chunkId]);
|
|
removeChunkIfEmpty(depTree, depTree.chunks[chunkId]);
|
|
checkObsolete(depTree, depTree.chunks[chunkId]);
|
|
}
|
|
// cleanup
|
|
delete depTree.chunkModules;
|
|
depTree.modulesByFile = depTree.modules;
|
|
depTree.modules = depTree.modulesById;
|
|
delete depTree.modulesById;
|
|
delete depTree.nextModuleId;
|
|
delete depTree.nextChunkId;
|
|
// return
|
|
callback(null, depTree);
|
|
}
|
|
}
|
|
|
|
function addModule(depTree, context, module, options, reason, callback) {
|
|
if(context)
|
|
resolve(context, module, options.resolve, resolved);
|
|
else
|
|
resolved(null, module);
|
|
function resolved(err, filename) {
|
|
if(err) {
|
|
callback(err);
|
|
return;
|
|
}
|
|
if(depTree.modules[filename]) {
|
|
depTree.modules[filename].reasons.push(reason);
|
|
callback(null, depTree.modules[filename].id);
|
|
} else {
|
|
var module = depTree.modules[filename] = {
|
|
id: depTree.nextModuleId++,
|
|
filename: filename,
|
|
reasons: [reason]
|
|
};
|
|
depTree.modulesById[module.id] = module;
|
|
fs.readFile(filename, "utf-8", function(err, source) {
|
|
if(err) {
|
|
callback(err);
|
|
return;
|
|
}
|
|
var deps;
|
|
try {
|
|
deps = parse(source, options.parse);
|
|
} catch(e) {
|
|
callback("File \"" + filename + "\" parsing failed: " + e);
|
|
return;
|
|
}
|
|
module.requires = deps.requires || [];
|
|
module.asyncs = deps.asyncs || [];
|
|
module.contexts = deps.contexts || [];
|
|
module.source = source;
|
|
|
|
var requires = {}, directRequire = {};
|
|
var contexts = [], directContexts = {};
|
|
function add(r) {
|
|
requires[r.name] = requires[r.name] || [];
|
|
requires[r.name].push(r);
|
|
}
|
|
function addContext(m) {
|
|
return function(c) {
|
|
contexts.push({context: c, module: m});
|
|
}
|
|
}
|
|
if(module.requires) {
|
|
module.requires.forEach(add);
|
|
module.requires.forEach(function(r) {
|
|
directRequire[r.name] = true;
|
|
});
|
|
}
|
|
if(module.contexts) {
|
|
module.contexts.forEach(addContext(module));
|
|
module.contexts.forEach(function(c) {
|
|
directContexts[c.name] = true;
|
|
});
|
|
}
|
|
if(module.asyncs)
|
|
module.asyncs.forEach(function addAsync(c) {
|
|
if(c.requires)
|
|
c.requires.forEach(add);
|
|
if(c.asyncs)
|
|
c.asyncs.forEach(addAsync);
|
|
if(c.contexts)
|
|
c.contexts.forEach(addContext(c));
|
|
});
|
|
requiresNames = Object.keys(requires);
|
|
var count = requiresNames.length + contexts.length + 1;
|
|
var errors = [];
|
|
if(requiresNames.length)
|
|
requiresNames.forEach(function(moduleName) {
|
|
var reason = {
|
|
type: directRequire[moduleName] ? "require" : "async require",
|
|
count: requires[moduleName].length,
|
|
filename: filename
|
|
};
|
|
addModule(depTree, path.dirname(filename), moduleName, options, reason, function(err, moduleId) {
|
|
if(err) {
|
|
errors.push(err+"\n @ " + filename + " (line " + requires[moduleName][0].line + ", column " + requires[moduleName][0].column + ")");
|
|
} else {
|
|
requires[moduleName].forEach(function(requireItem) {
|
|
requireItem.id = moduleId;
|
|
});
|
|
}
|
|
endOne();
|
|
});
|
|
});
|
|
if(contexts) {
|
|
contexts.forEach(function(contextObj) {
|
|
var context = contextObj.context;
|
|
var module = contextObj.module;
|
|
var reason = {
|
|
type: directContexts[context.name] ? "context" : "async context",
|
|
filename: filename
|
|
};
|
|
addContextModule(depTree, path.dirname(filename), context.name, options, reason, function(err, contextModuleId) {
|
|
if(err) {
|
|
errors.push(err+"\n @ " + filename + " (line " + context.line + ", column " + context.column + ")");
|
|
} else {
|
|
context.id = contextModuleId;
|
|
module.requires.push({id: context.id});
|
|
}
|
|
endOne();
|
|
});
|
|
if(context.warn) {
|
|
depTree.warnings.push(filename + " (line " + context.line + ", column " + context.column + "): " +
|
|
"implicit use of require.context(\".\") is not recommended.");
|
|
}
|
|
});
|
|
}
|
|
endOne();
|
|
function endOne() {
|
|
count--;
|
|
if(count === 0) {
|
|
if(errors.length) {
|
|
callback(errors.join("\n"));
|
|
} else {
|
|
callback(null, module.id);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
function addContextModule(depTree, context, contextModuleName, options, reason, callback) {
|
|
resolve.context(context, contextModuleName, options.resolve, resolved);
|
|
function resolved(err, dirname) {
|
|
if(err) {
|
|
callback(err);
|
|
return;
|
|
}
|
|
if(depTree.modules[dirname]) {
|
|
depTree.modules[dirname].reasons.push(reason);
|
|
callback(null, depTree.modules[dirname].id);
|
|
} else {
|
|
var contextModule = depTree.modules[dirname] = {
|
|
name: contextModuleName,
|
|
dirname: dirname,
|
|
id: depTree.nextModuleId++,
|
|
requireMap: {},
|
|
requires: [],
|
|
reasons: [reason]
|
|
};
|
|
depTree.modulesById[contextModule.id] = contextModule;
|
|
var extensions = (options.resolve && options.resolve.extensions) || [".web.js", ".js"];
|
|
function doDir(dirname, moduleName, done) {
|
|
fs.readdir(dirname, function(err, list) {
|
|
if(err) {
|
|
done(err);
|
|
} else {
|
|
var count = list.length + 1;
|
|
var errors = [];
|
|
function endOne(err) {
|
|
if(err) {
|
|
errors.push(err);
|
|
}
|
|
count--;
|
|
if(count == 0) {
|
|
if(errors.length > 0)
|
|
done(errors.join("\n"));
|
|
else
|
|
done();
|
|
}
|
|
}
|
|
list.forEach(function(file) {
|
|
var filename = path.join(dirname, file);
|
|
fs.stat(filename, function(err, stat) {
|
|
if(err) {
|
|
errors.push(err);
|
|
endOne();
|
|
} else {
|
|
if(stat.isDirectory()) {
|
|
if(file === "node_modules" || file === "web_modules")
|
|
endOne();
|
|
else
|
|
doDir(filename, moduleName + "/" + file, endOne);
|
|
} else {
|
|
var hasExt = false;
|
|
extensions.forEach(function(ext) {
|
|
if(file.substr(file.length - ext.length) === ext)
|
|
hasExt = true;
|
|
});
|
|
if(!hasExt) {
|
|
endOne();
|
|
return;
|
|
}
|
|
var modulereason = {
|
|
type: "context",
|
|
filename: reason.filename
|
|
};
|
|
addModule(depTree, null, filename, options, reason, function(err, moduleId) {
|
|
if(err) {
|
|
endOne(err);
|
|
} else {
|
|
contextModule.requires.push({id: moduleId});
|
|
contextModule.requireMap[moduleName + "/" + file] = moduleId;
|
|
endOne();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
});
|
|
endOne();
|
|
}
|
|
});
|
|
}
|
|
doDir(dirname, ".", function(err) {
|
|
if(err) {
|
|
callback(err);
|
|
return;
|
|
}
|
|
var extensionsAccess = [];
|
|
extensions.forEach(function(ext) {
|
|
extensionsAccess.push("||map[name+\"");
|
|
extensionsAccess.push(ext.replace(/\\/g, "\\\\").replace(/"/g, "\\\""));
|
|
extensionsAccess.push("\"]");
|
|
});
|
|
|
|
contextModule.source = "/***/module.exports = function(name) {\n" +
|
|
"/***/\tvar map = " + JSON.stringify(contextModule.requireMap) + ";\n" +
|
|
"/***/\treturn require(map[name]" + extensionsAccess.join("") + ");\n" +
|
|
"/***/};";
|
|
callback(null, contextModule.id);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
function addChunk(depTree, chunkStartpoint, options) {
|
|
var chunk = {
|
|
id: depTree.nextChunkId++,
|
|
modules: {},
|
|
context: chunkStartpoint
|
|
};
|
|
depTree.chunks[chunk.id] = chunk;
|
|
if(chunkStartpoint) {
|
|
chunkStartpoint.chunkId = chunk.id;
|
|
addModuleToChunk(depTree, chunkStartpoint, chunk.id, options);
|
|
}
|
|
return chunk;
|
|
}
|
|
|
|
function addModuleToChunk(depTree, context, chunkId, options) {
|
|
context.chunks = context.chunks || [];
|
|
if(context.chunks.indexOf(chunkId) === -1) {
|
|
context.chunks.push(chunkId);
|
|
if(context.id !== undefined)
|
|
depTree.chunks[chunkId].modules[context.id] = "include";
|
|
if(context.requires) {
|
|
context.requires.forEach(function(requireItem) {
|
|
addModuleToChunk(depTree, depTree.modulesById[requireItem.id], chunkId, options);
|
|
});
|
|
}
|
|
if(context.asyncs) {
|
|
context.asyncs.forEach(function(context) {
|
|
var subChunk
|
|
if(context.chunkId) {
|
|
subChunk = depTree.chunks[context.chunkId];
|
|
} else {
|
|
subChunk = addChunk(depTree, context, options);
|
|
}
|
|
subChunk.parents = subChunk.parents || [];
|
|
subChunk.parents.push(chunkId);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
function removeParentsModules(depTree, chunk) {
|
|
if(!chunk.parents) return;
|
|
for(var moduleId in chunk.modules) {
|
|
var inParent = true;
|
|
chunk.parents.forEach(function(parentId) {
|
|
if(!depTree.chunks[parentId].modules[moduleId])
|
|
inParent = false;
|
|
});
|
|
if(inParent) {
|
|
chunk.modules[moduleId] = "in-parent";
|
|
}
|
|
}
|
|
}
|
|
|
|
function removeChunkIfEmpty(depTree, chunk) {
|
|
var hasModules = false;
|
|
for(var moduleId in chunk.modules) {
|
|
if(chunk.modules[moduleId] === "include") {
|
|
hasModules = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!hasModules) {
|
|
chunk.context.chunkId = null;
|
|
chunk.empty = true;
|
|
}
|
|
}
|
|
|
|
function checkObsolete(depTree, chunk) {
|
|
var modules = [];
|
|
for(var moduleId in chunk.modules) {
|
|
if(chunk.modules[moduleId] === "include") {
|
|
modules.push(moduleId);
|
|
}
|
|
}
|
|
if(modules.length === 0) return;
|
|
modules.sort();
|
|
var moduleString = modules.join(" ");
|
|
if(depTree.chunkModules[moduleString]) {
|
|
chunk.equals = depTree.chunkModules[moduleString];
|
|
if(chunk.context)
|
|
chunk.context.chunkId = chunk.equals;
|
|
} else
|
|
depTree.chunkModules[moduleString] = chunk.id;
|
|
} |