allow require as identifier to convert to require.context(".")

This commit is contained in:
Tobias Koppers 2012-03-14 16:33:46 +01:00
parent b675c7f13b
commit 0bc303cbf3
12 changed files with 158 additions and 25 deletions

View File

@ -119,6 +119,9 @@ function getTemplate(templateName) {
See [details](modules-webpack/tree/master/examples/require.context) for complete example.
When try to store the `require` function in another variable or try to pass it as parameter,
`webpack` convert it to a `require.context(".")` to be combatible.
There is a warning emitted in this case.
*Warning: The complete code in the directory are included. So use it carefully.*
@ -128,12 +131,19 @@ See [details](modules-webpack/tree/master/examples/require.context) for complete
As dependencies are resolved before running:
* `require` should not be overwritten
* `require` should not be called indirect as `var r = require; r("./a");`. Use `require.context`?
* `require` should not be overwritten by variable declaration (`var require = ...`), by function parameter is allowed `function(require) {...}`.
* `require.ensure` should not be overwritten or called indirect
* the function passed to `require.ensure` should be inlined in the call.
* `require.context` should not be overwritten or called indirect
* the argument to `require.context` should be a literal or addition of multiple literals
* An indirect call of `require` should access a file in current directory: This throws an exception: `var r = require; r("../file");`
The following cases could result in too much code in result file if used wrong:
* indirect call of `require`: `var r = require; r("./file");`
* `require.context`. It includes the whole directory.
* expressions in require arguments: `require(variable)`, `require(condition ? "a" : "b")` (TODO)
* the function passed to `require.ensure` is not inlined in the call.
### node.js specific modules
@ -147,6 +157,8 @@ web_modules
...
```
TODO provide some replacements
## Usage
### Shell
@ -167,6 +179,7 @@ Options:
--options Options JSON File [string]
--script-src-prefix Path Prefix For JavaScript Loading [string]
--libary Stores the exports into this variable [string]
--colors Output Stats with colors [boolean] [default: false]
```
### Programmatically Usage
@ -220,7 +233,7 @@ However `webpack` has big differences:
`webpack` replaces module names and paths with numbers. `webmake` don't do that and do resolves requires on client-side.
This design of `webmake` was intended to support variables as arguments to require calls.
`webpack` resolves requires in compile time and have no resolve code on client side. This results in smaller bundles.
Variables as argments will be handled different and with more limitations in `webpack`.
Variables as arguments will be handled different and with more limitations in `webpack`.
Another limitation in `webmake` which are based on the previous one is that modules must be in the current package scope.
In `webpack` this is not a restriction.

View File

@ -30,6 +30,10 @@ var argv = require("optimist")
.string("libary")
.describe("libary", "Stores the exports into this variable")
.boolean("colors")
.describe("colors", "Output Stats with colors")
.default("colors", false)
.demand(1)
.argv;
@ -92,6 +96,6 @@ if(argv.single) {
console.error(err);
return;
}
console.log(stats);
console.log(require("util").inspect(stats, false, 10, argv.colors));
});
}

View File

@ -21,6 +21,7 @@ module.exports = function buildDeps(context, mainModule, options, callback) {
if(!options) options = {};
var depTree = {
warnings: [],
modules: {},
modulesById: {},
chunks: {},
@ -71,7 +72,13 @@ function addModule(depTree, context, module, options, callback) {
callback(err);
return;
}
var deps = parse(source);
var deps;
try {
deps = parse(source);
} catch(e) {
callback("File \"" + filename + "\" parsing failed: " + e);
return;
}
module.requires = deps.requires || [];
module.asyncs = deps.asyncs || [];
module.contexts = deps.contexts || [];
@ -129,7 +136,11 @@ function addModule(depTree, context, module, options, callback) {
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();
@ -165,6 +176,7 @@ function addContextModule(depTree, context, contextModuleName, options, callback
requires: []
};
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) {
@ -194,6 +206,15 @@ function addContextModule(depTree, context, contextModuleName, options, callback
if(stat.isDirectory()) {
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;
}
addModule(depTree, null, filename, options, function(err, moduleId) {
if(err) {
endOne(err);
@ -217,7 +238,6 @@ function addContextModule(depTree, context, contextModuleName, options, callback
return;
}
var extensionsAccess = [];
var extensions = (options.resolve && options.resolve.extensions) || [".web.js", ".js"];
extensions.forEach(function(ext) {
extensionsAccess.push("||map[name+\"");
extensionsAccess.push(ext.replace(/"/g, "\\\""));

View File

@ -78,10 +78,17 @@ function walkStatement(context, statement) {
// Declarations
case "FunctionDeclaration":
var req = functionParamsContainsRequire(statement.params);
if(req) {
var old = context.requireOverwrite;
context.requireOverwrite = true;
}
if(statement.body.type === "BlockStatement")
walkStatement(context, statement.body);
else
walkExpression(context, statement.body);
if(req)
context.requireOverwrite = old;
break;
case "VariableDeclaration":
if(statement.declarations)
@ -135,10 +142,21 @@ function walkExpression(context, expression) {
});
break;
case "FunctionExpression":
var req = functionParamsContainsRequire(expression.params);
if(context.paramMustRequire) {
req = false;
context.paramMustRequire = false;
}
if(req) {
var old = context.requireOverwrite;
context.requireOverwrite = true;
}
if(expression.body.type === "BlockStatement")
walkStatement(context, expression.body);
else
walkExpression(context, expression.body);
if(req)
context.requireOverwrite = old;
break;
case "SequenceExpression":
if(expression.expressions)
@ -165,7 +183,9 @@ function walkExpression(context, expression) {
walkExpressions(context, expression.arguments);
break;
case "CallExpression":
if(expression.callee && expression.arguments &&
var noCallee = false;
if(!context.requireOverwrite &&
expression.callee && expression.arguments &&
expression.arguments.length == 1 &&
expression.callee.type === "Identifier" &&
expression.callee.name === "require") {
@ -173,13 +193,20 @@ function walkExpression(context, expression) {
if(param.code) {
// make context
var pos = param.value.indexOf("/");
context.contexts = context.contexts || [];
if(pos === -1) {
throw new Error("require a module by variable is not supported");
var newContext = {
name: ".",
require: true,
calleeRange: expression.callee.range,
line: expression.loc.start.line,
column: expression.loc.start.column
};
context.contexts.push(newContext);
} else {
var match = /\/[^\/]*$/.exec(param.value);
var dirname = param.value.substring(0, match.index);
var remainder = "." + param.value.substring(match.index);
context.contexts = context.contexts || [];
var newContext = {
name: dirname,
require: true,
@ -200,8 +227,10 @@ function walkExpression(context, expression) {
column: expression.loc.start.column
});
}
noCallee = true;
}
if(expression.callee && expression.arguments &&
if(!context.requireOverwrite &&
expression.callee && expression.arguments &&
expression.arguments.length >= 1 &&
expression.callee.type === "MemberExpression" &&
expression.callee.object.type === "Identifier" &&
@ -214,15 +243,18 @@ function walkExpression(context, expression) {
requires: [],
namesRange: expression.arguments[0].range,
line: expression.loc.start.line,
column: expression.loc.start.column
column: expression.loc.start.column,
paramMustRequire: true
};
param.forEach(function(r) {
newContext.requires.push({name: r});
});
context.asyncs.push(newContext);
context = newContext;
noCallee = true;
}
if(expression.callee && expression.arguments &&
if(!context.requireOverwrite &&
expression.callee && expression.arguments &&
expression.arguments.length == 1 &&
expression.callee.type === "MemberExpression" &&
expression.callee.object.type === "Identifier" &&
@ -238,9 +270,10 @@ function walkExpression(context, expression) {
column: expression.loc.start.column,
};
context.contexts.push(newContext);
noCallee = true;
}
if(expression.callee)
if(expression.callee && !noCallee)
walkExpression(context, expression.callee);
if(expression.arguments)
walkExpressions(context, expression.arguments);
@ -250,9 +283,35 @@ function walkExpression(context, expression) {
if(expression.property.type !== "Identifier")
walkExpression(context, expression.property);
break;
case "Identifier":
if(!context.requireOverwrite &&
expression.name === "require") {
context.contexts = context.contexts || [];
var newContext = {
name: ".",
warn: "Identifier",
require: true,
calleeRange: [expression.range[0], expression.range[1]],
line: expression.loc.start.line,
column: expression.loc.start.column,
};
context.contexts.push(newContext);
}
break;
}
}
function functionParamsContainsRequire(params) {
if(!params) return false;
var found = false;
params.forEach(function(param) {
if(param.type === "Identifier" &&
param.name === "require")
found = true;
});
return found;
}
function parseString(expression) {
switch(expression.type) {
case "BinaryExpression":

View File

@ -72,6 +72,7 @@ module.exports = function(context, moduleName, options, callback) {
options.outputPostfix = "." + options.output;
}
var fileSizeMap = {};
var fileModulesMap = {};
var chunksCount = 0;
for(var chunkId in depTree.chunks) {
var chunk = depTree.chunks[chunkId];
@ -115,6 +116,12 @@ module.exports = function(context, moduleName, options, callback) {
if(err) throw err;
});
fileSizeMap[path.basename(filename)] = buffer.length;
var modulesArray = [];
for(var moduleId in chunk.modules) {
if(chunk.modules[moduleId] === "include")
modulesArray.push({id: moduleId, filename: depTree.modulesById[moduleId].filename});
}
fileModulesMap[path.basename(filename)] = modulesArray;
}
buffer = {};
buffer.chunkCount = chunksCount;
@ -135,6 +142,8 @@ module.exports = function(context, moduleName, options, callback) {
}
buffer.modulesFirstChunk = sum;
buffer.fileSizes = fileSizeMap;
buffer.warnings = depTree.warnings;
buffer.fileModules = fileModulesMap;
callback(null, buffer);
} else {
if(options.libary) {

View File

@ -31,11 +31,12 @@ module.exports = function(module) {
to: contextItem.calleeRange[1],
value: "require(" + prefix + ((contextItem.id || "throw new Error('there is not id for this')") + "") + ")"
});
replaces.push({
from: contextItem.replace[0][0],
to: contextItem.replace[0][1],
value: stringify(contextItem.replace[1])
});
if(contextItem.replace)
replaces.push({
from: contextItem.replace[0][0],
to: contextItem.replace[0][1],
value: stringify(contextItem.replace[1])
});
} else {
replaces.push({
from: contextItem.expressionRange[0],

View File

@ -1,6 +1,6 @@
{
"name": "webpack",
"version": "0.1.6",
"version": "0.2.0",
"author": "Tobias Koppers @sokra",
"description": "Packs CommonJs Modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand.",
"dependencies": {

View File

@ -9,21 +9,21 @@ argv.shift();
argv.shift();
var extraArgs = argv.join(" ");
cp.exec("node ../../bin/webpack.js "+extraArgs+" --single --libary libary1 node_modules/libary1 js/libary1.js", function (error, stdout, stderr) {
cp.exec("node ../../bin/webpack.js "+extraArgs+" --colors --single --libary libary1 node_modules/libary1 js/libary1.js", function (error, stdout, stderr) {
console.log('libary1 stdout:\n' + stdout);
console.log('libary1 stderr:\n ' + stderr);
if (error !== null) {
console.log('libary1 error: ' + error);
}
});
cp.exec("node ../../bin/webpack.js "+extraArgs+" --script-src-prefix js/ --libary libary2 node_modules/libary2 js/libary2.js", function (error, stdout, stderr) {
cp.exec("node ../../bin/webpack.js "+extraArgs+" --colors --script-src-prefix js/ --libary libary2 node_modules/libary2 js/libary2.js", function (error, stdout, stderr) {
console.log('libary2 stdout:\n' + stdout);
console.log('libary2 stderr:\n ' + stderr);
if (error !== null) {
console.log('libary2 error: ' + error);
}
});
cp.exec("node ../../bin/webpack.js "+extraArgs+" --script-src-prefix js/ lib/index js/web.js", function (error, stdout, stderr) {
cp.exec("node ../../bin/webpack.js "+extraArgs+" --colors --script-src-prefix js/ lib/index js/web.js", function (error, stdout, stderr) {
console.log('web stdout:\n' + stdout);
console.log('web stderr:\n ' + stderr);
if (error !== null) {

View File

@ -18,8 +18,16 @@ window.test(require("./singluar").value === 2, "exported object is singluar");
window.test(require("subfilemodule") === "subfilemodule", "Modules as single file should load");
window.test(require.context("../templates")("./tmpl") === "test template", "Context should work");
window.test(require . context ( "." + "." + "/" + "templ" + "ates" ) ( "./subdir/tmpl.js" ) === "subdir test template", "Context should work with subdirectories and splitted");
var template = "tmpl";
var template = "tmpl", templateFull = "./tmpl.js";
window.test(require("../templates/" + template) === "test template", "Automatical context should work");
window.test(require("../templates/templateLoader")(templateFull) === "test template", "Automatical context without prefix should work");
window.test(require("../templates/templateLoaderIndirect")(templateFull) === "test template", "Automatical context should work with require identifier");
window.test(function(require) { return require; }(1234) === 1234, "require overwrite in anonymos function");
function testFunc(abc, require) {
return require;
}
window.test(testFunc(333, 678) === 678, "require overwrite in named function");
require.ensure([], function(require) {
var contextRequire = require.context(".");

View File

@ -0,0 +1,5 @@
This is a file!
with some content
it should break webpack :)

View File

@ -0,0 +1,3 @@
module.exports = function(name) {
return require(name);
}

View File

@ -0,0 +1,11 @@
module.exports = function(name) {
var a = load(require, name);
var r = require;
var b = r(name);
if(a !== b) return "FAIL";
return a;
}
function load(requireFunction, name) {
return requireFunction(name);
}