Initial commit

This commit is contained in:
Tobias Koppers 2012-03-10 13:11:23 +01:00
commit 2e1460036c
67 changed files with 1724 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/node_modules
/test/js
/test/browsertest/js
/example/js

217
README.md Normal file
View File

@ -0,0 +1,217 @@
# modules-webpack
## Goal
As developer you want to reuse existing code.
As with node.js and web all file are already in the same language, but it is extra work to use your code with the node.js module system and the browser.
The goal of `webpack` is to bundle CommonJs modules into javascript files which can be loaded by `<script>`-tags.
Concating all required file has a disadvantage: many code to download (and execute) on page load.
Therefor `webpack` uses the `require.ensure` function to split your code automatically into multiple bundles which are loaded on demand.
This happens mostly transparent to the developer with a single function call. Dependencies are resolved for you.
The result is a smaller inital code download which results in faster page load.
**TL;DR**
* bundle CommonJs modules
* create multiple files which are loaded on demand
* dependencies managed for you
* faster page load in big webapps
## Example
``` javascript
// a.js
var b = require("./b");
b.stuff("It works");
// b.js
exports.stuff = function(text) {
console.log(text);
}
```
are compiled to
``` javascript
(/* small webpack header */)
({
0: function(module, exports, require) {
var b = require(1);
b.stuff("It works");
},
1: function(module, exports, require) {
exports.stuff = function(text) {
console.log(text);
}
}
})
```
## Browser replacements
Somethings it happens that browsers require other code than node.js do.
`webpack` allow module developers to specify replacements which are used in the compile process of `webpack`.
Modules in `web_modules` replace modules in `node_modules`.
`filename.web.js` replaces `filename.js` when required without file extention.
TODO specify replacements in options
## Limitations
### `require`-function
As dependencies are resolved before running:
* `require` should not be overwritten
* `require` should not be called indirect as `var r = require; r("./a");`
* arguments of `require` should be literals. `"./abc" + "/def"` is allowed to support long lines.
* `require.ensure` has the same limitations as `require`
* the function passed to `require.ensure` should be inlined in the call.
TODO allow variables passing to `require` like `require("./templates/" + mytemplate)`
(this will cause all modules matching this pattern to be included in addition to a mapping table)
### node.js specific modules
As node.js specific modules like `fs` will not work in browser they are not included and cause an error.
You should replace them be own modules if your use them.
```
web_modules
fs
path
...
```
## Code Splitting
### Example
``` javascript
var a = require("a");
var b = require("b");
require.ensure(["c"], function(require) {
require("b").xyz();
var d = require("d");
});
```
```
File 1: web.js
- code of module a and dependencies
- code of module b and dependencies
File 2: 1.web.js
- code of module c and dependencies (but code is not used)
- code of module d and dependencies
```
See [details](modules-webpack/tree/master/example) for exact output.
## Usage
### Shell
`webpack` offers a command line interface:
after `npm install webpack -g` you can use the `webpack` command
if invoked without arguments it prints a usage:
```
Usage: webpack <options> <input> <output>
Options:
--single Disable Code Splitting [boolean] [default: false]
--min Minimize it with uglifyjs [boolean] [default: false]
--filenames Output Filenames Into File [boolean] [default: false]
--options Options JSON File [string]
--script-src-prefix Path Prefix For JavaScript Loading [string]
--libary Stores the exports into this variable [string]
```
### Programmatically Usage
`webpack(context, moduleName, [options], callback)`
`webpack(absoluteModulePath, [options], callback)`
#### `options`
you can save this options object in a JSON file and use it with the shell command.
`outputJsonpFunction`
JSONP function used to load chunks
`scriptSrcPrefix`
Path from where chunks are loaded
`outputDirectory`
write files to this directory (absolute path)
`output`
write first chunk to this file
`outputPostfix`
write chunks to files named chunkId plus outputPostfix
`libary`
exports of input file are stored in this variable
`minimize`
minimize outputs with uglify-js
`includeFilenames`
add absolute filenames of input files as comments
#### `callback`
`function(err, source / stats)`
`source` if `options.output` is not set
else `stats` as json see [example](/modules-webpack/tree/master/example)
## medikoo/modules-webmake
`webpack` as originally intended as fork for `webmake` for @medikoo so it shared several ideas with it.
So big credit goes to medikoo.
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` wes 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.
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.
The design of `webmake` causes all modules with the same name to overlap. This can be problematic if different submodules rely on specific versions of the same module. The behaivior also differs from the behaivior of node.js, because node.js installs a module for each instance in submodules and `webmake` cause them the merge into a single module which is only installed once. In `webpack` this is not the case. Different versions do not overlap and modules are installed multiple times. But in `webpack` this can (currently) cause duplicate code if a module is used in multiple modules. I want to face this issue (TODO).
`webmake` do (currently) not support Code Splitting.
## Tests
You can run the unit tests which `node_modules/.bin/vows`.
You can run the browser tests:
```
cd test/browsertests
node build
```
and open `test.html` in browser. There must be several OKs in the file and no FAIL.
TODO more tests
## Contribution
You are welcome to contribute by writing issues or pull requests.
You are also welcome to correct any spelling mistakes or any language issues, because my english is not so good...
## License
MIT

93
bin/webpack.js Normal file
View File

@ -0,0 +1,93 @@
#!/usr/bin/env node
var path = require("path");
var fs = require("fs");
var argv = require("optimist")
.usage("Usage: $0 <input> <output>")
.boolean("single")
.describe("single", "Disable Code Splitting")
.default("single", false)
.boolean("min")
.describe("min", "Minimize it with uglifyjs")
.default("min", false)
.boolean("filenames")
.describe("filenames", "Output Filenames Into File")
.default("filenames", false)
.string("options")
.describe("options", "Options JSON File")
.string("script-src-prefix")
.describe("script-src-prefix", "Path Prefix For JavaScript Loading")
.string("libary")
.describe("libary", "Stores the exports into this variable")
.demand(1)
.argv;
var input = argv._[0],
output = argv._[1];
if (input && input[0] !== '/' && input[1] !== ':') {
input = path.join(process.cwd(), input);
}
if (output && output[0] !== '/' && input[1] !== ':') {
output = path.join(process.cwd(), output);
}
var options = {};
if(argv.options) {
options = JSON.parse(fs.readFileSync(argv.options, "utf-8"));
}
if(argv["script-src-prefix"]) {
options.scriptSrcPrefix = argv["script-src-prefix"];
}
if(argv.min) {
options.minimize = true;
}
if(argv.filenames) {
options.includeFilenames = true;
}
if(argv.libary) {
options.libary = argv.libary;
}
var webpack = require("../lib/webpack.js");
if(argv.single) {
webpack(input, options, function(err, source) {
if(err) {
console.error(err);
return;
}
if(output) {
fs.writeFileSync(output, source, "utf-8");
} else {
process.stdout.write(source);
}
});
} else {
output = output || path.join(process.cwd(), "js", "web.js");
if(!options.outputDirectory) options.outputDirectory = path.dirname(output);
if(!options.output) options.output = path.basename(output);
if(!options.outputPostfix) options.outputPostfix = "." + path.basename(output);
var outExists = path.existsSync(options.outputDirectory);
if(!outExists)
fs.mkdirSync(options.outputDirectory);
webpack(input, options, function(err, stats) {
if(err) {
console.error(err);
return;
}
console.log(stats);
});
}

126
example/README.md Normal file
View File

@ -0,0 +1,126 @@
# example.js
``` javascript
var a = require("a");
var b = require("b");
require.ensure(["c"], function(require) {
require("b").xyz();
var d = require("d");
});
```
# js/output.js
``` javascript
/******/(function(document, undefined) {
/******/ return function(modules) {
/******/ var installedModules = {}, installedChunks = {0:1};
/******/ function require(moduleId) {
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ var module = installedModules[moduleId] = {
/******/ exports: {}
/******/ };
/******/ modules[moduleId](module, module.exports, require);
/******/ return module.exports;
/******/ }
/******/ require.ensure = function(chunkId, callback) {
/******/ if(installedChunks[chunkId] === 1) return callback(require);
/******/ if(installedChunks[chunkId] !== undefined)
/******/ installedChunks[chunkId].push(callback);
/******/ else {
/******/ installedChunks[chunkId] = [callback];
/******/ var head = document.getElementsByTagName('head')[0];
/******/ var script = document.createElement('script');
/******/ script.type = 'text/javascript';
/******/ script.src = modules.c+chunkId+modules.a;
/******/ head.appendChild(script);
/******/ }
/******/ };
/******/ window[modules.b] = function(chunkId, moreModules) {
/******/ for(var moduleId in moreModules)
/******/ modules[moduleId] = moreModules[moduleId];
/******/ var callbacks = installedChunks[chunkId];
/******/ installedChunks[chunkId] = 1;
/******/ for(var i = 0; i < callbacks.length; i++)
/******/ callbacks[i](require);
/******/ };
/******/ return require(0);
/******/ }
/******/})(document)
/******/({a:".output.js",b:"webpackJsonp",c:"",
/******/0: function(module, exports, require) {
var a = require(1);
var b = require(2);
require.ensure(1, function(require) {
require(2).xyz();
var d = require(3);
});
/******/},
/******/
/******/1: function(module, exports, require) {
// module a
/******/},
/******/
/******/2: function(module, exports, require) {
// module b
/******/},
/******/
/******/})
```
# 1.output.js
``` javascript
/******/webpackJsonp(1, {
/******/3: function(module, exports, require) {
// module d
/******/},
/******/
/******/4: function(module, exports, require) {
// module c
/******/},
/******/
/******/})
```
Minimized
``` javascript
webpackJsonp(1,{3:function(a,b,c){},4:function(a,b,c){}})
```
# Info
## Uncompressed
``` javascript
{ chunkCount: 2,
modulesCount: 5,
modulesIncludingDuplicates: 5,
modulesPerChunk: 2.5,
modulesFirstChunk: 3,
fileSizes: { 'output.js': 1948, '1.output.js': 200 } }
```
## Compress (uglify-js, no zip)
``` javascript
{ chunkCount: 2,
modulesCount: 5,
modulesIncludingDuplicates: 5,
modulesPerChunk: 2.5,
modulesFirstChunk: 3,
fileSizes: { 'output.js': 661, '1.output.js': 57 } }
```

14
example/build.js Normal file
View File

@ -0,0 +1,14 @@
var cp = require('child_process');
var argv = process.argv;
argv.shift();
argv.shift();
var extraArgs = argv.join(" ");
cp.exec("node ../bin/webpack.js "+extraArgs+" example.js js/output.js", function (error, stdout, stderr) {
console.log('stdout:\n' + stdout);
console.log('stderr:\n ' + stderr);
if (error !== null) {
console.log('error: ' + error);
}
});

6
example/example.js Normal file
View File

@ -0,0 +1,6 @@
var a = require("a");
var b = require("b");
require.ensure(["c"], function(require) {
require("b").xyz();
var d = require("d");
});

1
example/node_modules/a.js generated vendored Normal file
View File

@ -0,0 +1 @@
// module a

1
example/node_modules/b.js generated vendored Normal file
View File

@ -0,0 +1 @@
// module b

1
example/node_modules/c.js generated vendored Normal file
View File

@ -0,0 +1 @@
// module c

1
example/node_modules/d.js generated vendored Normal file
View File

@ -0,0 +1 @@
// module d

201
lib/buildDeps.js Normal file
View File

@ -0,0 +1,201 @@
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 = {
modules: {},
modulesById: {},
chunks: {},
nextModuleId: 0,
nextChunkId: 0,
chunkModules: {} // used by checkObsolete
}
var mainModuleId;
addModule(depTree, context, mainModule, options, 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]);
}
callback(null, depTree);
}
}
function addModule(depTree, context, module, options, callback) {
resolve(context, module, options.resolve, function(err, filename) {
if(err) {
callback(err);
return;
}
if(depTree.modules[filename]) {
callback(null, depTree.modules[filename].id);
} else {
var module = depTree.modules[filename] = {
id: depTree.nextModuleId++,
filename: filename
};
depTree.modulesById[module.id] = module;
fs.readFile(filename, "utf-8", function(err, source) {
if(err) {
callback(err);
return;
}
var deps = parse(source);
module.requires = deps.requires || [];
module.asyncs = deps.asyncs || [];
module.source = source;
var requires = {};
function add(r) {
requires[r.name] = requires[r.name] || [];
requires[r.name].push(r);
}
if(module.requires)
module.requires.forEach(add);
if(module.asyncs)
module.asyncs.forEach(function addContext(c) {
if(c.requires)
c.requires.forEach(add);
if(c.asyncs)
c.asyncs.forEach(addContext);
});
requiresNames = Object.keys(requires);
var count = requiresNames.length;
var errors = [];
if(requiresNames.length)
requiresNames.forEach(function(moduleName) {
addModule(depTree, path.dirname(filename), moduleName, options, 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;
});
}
count--;
if(count === 0) {
if(errors.length) {
callback(errors.join("\n"));
} else {
end();
}
}
});
});
else end()
function end() {
callback(null, module.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 = false;
chunk.parents.forEach(function(parentId) {
if(depTree.chunks[parentId].modules[moduleId])
inParent = true;
});
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;
}

242
lib/parse.js Normal file
View File

@ -0,0 +1,242 @@
var esprima = require("esprima");
// Syntax: https://developer.mozilla.org/en/SpiderMonkey/Parser_API
function walkStatements(context, statements) {
statements.forEach(function(statement) {
walkStatement(context, statement);
});
}
function walkStatement(context, statement) {
switch(statement.type) {
// Real Statements
case "BlockStatement":
walkStatements(context, statement.body);
break;
case "ExpressionStatement":
walkExpression(context, statement.expression);
break;
case "IfStatement":
walkExpression(context, statement.test);
walkStatement(context, statement.consequent);
if(statement.alternate)
walkStatement(context, statement.alternate);
break;
case "LabeledStatement":
walkStatement(context, statement.body);
break;
case "WithStatement":
walkExpression(context, statement.object);
walkStatement(context, statement.body);
break;
case "SwitchStatement":
walkExpression(context, statement.discriminant);
walkSwitchCases(context, statement.cases);
break;
case "ReturnStatement":
case "ThrowStatement":
if(statement.argument)
walkExpression(context, statement.argument);
break;
case "TryStatement":
walkStatement(context, statement.block);
walkCatchClauses(context, statement.handlers);
if(statement.finalizer)
walkStatement(context, statement.finalizer);
break;
case "WhileStatement":
case "DoWhileStatement":
walkExpression(context, statement.test);
walkStatement(context, statement.body);
break;
case "ForStatement":
if(statement.init) {
if(statement.init.type === "VariableDeclaration")
walkStatement(context, statement.init);
else
walkExpression(context, statement.init);
}
if(statement.test)
walkExpression(context, statement.test);
if(statement.update)
walkExpression(context, statement.update);
walkStatement(context, statement.body);
break;
case "ForInStatement":
if(statement.left.type === "VariableDeclaration")
walkStatement(context, statement.left);
else
walkExpression(context, statement.left);
walkExpression(context, statement.right);
walkStatement(context, statement.body);
break;
// Declarations
case "FunctionDeclaration":
if(statement.body.type === "BlockStatement")
walkStatement(context, statement.body);
else
walkExpression(context, statement.body);
break;
case "VariableDeclaration":
if(statement.declarations)
walkVariableDeclarators(context, statement.declarations);
break;
}
}
function walkSwitchCases(context, switchCases) {
switchCases.forEach(function(switchCase) {
if(switchCase.test)
walkExpression(context, switchCase.test);
walkStatements(context, switchCase.consequent);
});
}
function walkCatchClauses(context, catchClauses) {
catchClauses.forEach(function(catchClause) {
if(catchClause.guard)
walkExpression(context, catchClause.guard);
walkStatement(context, catchClause.body);
});
}
function walkVariableDeclarators(context, declarators) {
declarators.forEach(function(declarator) {
switch(declarator.type) {
case "VariableDeclarator":
if(declarator.init)
walkExpression(context, declarator.init);
break;
}
});
}
function walkExpressions(context, expressions) {
expressions.forEach(function(expression) {
walkExpression(context, expression);
});
}
function walkExpression(context, expression) {
switch(expression.type) {
case "ArrayExpression":
if(expression.elements)
walkExpressions(context, expression.elements);
break;
case "ObjectExpression":
expression.properties.forEach(function(prop) {
walkExpression(context, prop.value);
});
break;
case "FunctionExpression":
if(expression.body.type === "BlockStatement")
walkStatement(context, expression.body);
else
walkExpression(context, expression.body);
break;
case "SequenceExpression":
if(expression.expressions)
walkExpressions(context, expression.expressions);
break;
case "UnaryExpression":
case "UpdateExpression":
walkExpression(context, expression.argument);
break;
case "BinaryExpression":
case "AssignmentExpression":
case "LogicalExpression":
walkExpression(context, expression.left);
walkExpression(context, expression.right);
break;
case "ConditionalExpression":
walkExpression(context, expression.test);
walkExpression(context, expression.alternate);
walkExpression(context, expression.consequent);
break;
case "NewExpression":
if(expression.arguments)
walkExpressions(context, expression.arguments);
break;
case "CallExpression":
if(expression.callee && expression.arguments &&
expression.arguments.length >= 1 &&
expression.callee.type === "Identifier" &&
expression.callee.name === "require") {
var param = parseString(expression.arguments[0]);
context.requires = context.requires || [];
context.requires.push({
name: param,
nameRange: expression.arguments[0].range,
line: expression.loc.start.line,
column: expression.loc.start.column
});
}
if(expression.callee && expression.arguments &&
expression.arguments.length >= 1 &&
expression.callee.type === "MemberExpression" &&
expression.callee.object.type === "Identifier" &&
expression.callee.object.name === "require" &&
expression.callee.property.type === "Identifier" &&
expression.callee.property.name in {async:1, ensure:1}) {
var param = parseStringArray(expression.arguments[0]);
context.asyncs = context.asyncs || [];
var newContext = {
requires: [],
namesRange: expression.arguments[0].range,
line: expression.loc.start.line,
column: expression.loc.start.column
};
param.forEach(function(r) {
newContext.requires.push({name: r});
});
context.asyncs.push(newContext);
context = newContext;
}
if(expression.callee)
walkExpression(context, expression.callee);
if(expression.arguments)
walkExpressions(context, expression.arguments);
break;
case "MemberExpression":
walkExpression(context, expression.object);
if(expression.property.type !== "Identifier")
walkExpression(context, expression.property);
break;
}
}
function parseString(expression) {
switch(expression.type) {
case "BinaryExpression":
return parseString(expression.left) + parseString(expression.right);
case "Literal":
if(typeof expression.value === "string")
return expression.value;
}
throw new Error(expression.type + " is not supported as parameter for require");
}
function parseStringArray(expression) {
switch(expression.type) {
case "ArrayExpression":
var arr = [];
if(expression.elements)
expression.elements.forEach(function(expr) {
arr.push(parseString(expr));
});
return arr;
}
return [parseString(expression)];
}
module.exports = function parse(source, options) {
var ast = esprima.parse(source, {range: true, loc: true});
if(!ast || typeof ast != "object")
throw new Error("Source couldn't be parsed");
var context = {};
walkStatements(context, ast.body);
return context;
}

136
lib/resolve.js Normal file
View File

@ -0,0 +1,136 @@
var path = require("path");
var fs = require("fs");
// http://nodejs.org/docs/v0.4.8/api/all.html#all_Together...
/**
* context: absolute filename of current file
* identifier: module to find
* options:
* paths: array of lookup paths
* callback: function(err, absoluteFilename)
*/
module.exports = function resolve(context, identifier, options, callback) {
if(!callback) {
callback = options;
options = {};
}
if(!options)
options = {};
if(!options.extensions)
options.extensions = [".web.js", ".js"];
if(!options.paths)
options.paths = [];
function finalResult(err, absoluteFilename) {
if(err) {
callback("Module \"" + identifier + "\" not found in context \"" + context + "\"");
return;
}
callback(null, absoluteFilename);
}
var identArray = identifier.split("/");
var contextArray = split(context);
if(identArray[0] === "." || identArray[0] === ".." || identArray[0] === "") {
var pathname = join(contextArray, identArray);
loadAsFile(pathname, options, function(err, absoluteFilename) {
if(err) {
loadAsDirectory(pathname, options, finalResult);
return;
}
callback(null, absoluteFilename);
});
} else {
loadNodeModules(contextArray, identArray, options, finalResult);
}
}
function split(a) {
return a.split(/[\/\\]/g);
}
function join(a, b) {
var c = [];
a.forEach(function(x) { c.push(x) });
b.forEach(function(x) { c.push(x) });
return path.join.apply(path, c);
}
function loadAsFile(filename, options, callback) {
var pos = -1, result;
function tryCb(err, stats) {
if(err || !stats || !stats.isFile()) {
pos++;
if(pos >= options.extensions.length) {
callback(err);
return;
}
fs.stat(result = filename + options.extensions[pos], tryCb);
return;
}
callback(null, result);
}
fs.stat(result = filename, tryCb);
}
function loadAsDirectory(dirname, options, callback) {
var packageJsonFile = join(split(dirname), ["package.json"]);
fs.stat(packageJsonFile, function(err, stats) {
var mainModule = "index";
if(!err && stats.isFile()) {
fs.readFile(packageJsonFile, "utf-8", function(err, content) {
content = JSON.parse(content);
if(content.main)
mainModule = content.main;
loadAsFile(join(split(dirname), [mainModule]), options, callback);
});
} else
loadAsFile(join(split(dirname), [mainModule]), options, callback);
});
}
function loadNodeModules(context, identifier, options, callback) {
nodeModulesPaths(context, options, function(err, dirs) {
function tryDir(dir) {
var pathname = join(split(dir), identifier);
loadAsFile(pathname, options, function(err, absoluteFilename) {
if(err) {
loadAsDirectory(pathname, options, function(err, absoluteFilename) {
if(err) {
if(dirs.length === 0) {
callback(true);
return;
}
tryDir(dirs.shift());
return;
}
callback(null, absoluteFilename);
});
return;
}
callback(null, absoluteFilename);
});
}
tryDir(dirs.shift());
});
}
function nodeModulesPaths(context, options, callback) {
var parts = context;
var rootNodeModules = parts.indexOf("node_modules");
var rootWebModules = parts.indexOf("web_modules");
var root = 0;
if(rootWebModules != -1 && rootNodeModules != -1)
root = Math.min(rootWebModules, rootNodeModules)-1;
else if(rootWebModules != -1 || rootNodeModules != -1)
root = Math.max(rootWebModules, rootNodeModules)-1;
var dirs = [];
options.paths.forEach(function(path) { dirs.push(path) });
for(var i = parts.length; i > root; i--) {
if(parts[i-1] === "node_modules" || parts[i-1] === "web_modules")
continue;
var part = parts.slice(0, i);
dirs.push(join(part, ["web_modules"]));
dirs.push(join(part, ["node_modules"]));
}
callback(null, dirs);
}

36
lib/templateAsync.js Normal file
View File

@ -0,0 +1,36 @@
/******/(function(document, undefined) {
/******/ return function(modules) {
/******/ var installedModules = {}, installedChunks = {0:1};
/******/ function require(moduleId) {
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ var module = installedModules[moduleId] = {
/******/ exports: {}
/******/ };
/******/ modules[moduleId](module, module.exports, require);
/******/ return module.exports;
/******/ }
/******/ require.ensure = function(chunkId, callback) {
/******/ if(installedChunks[chunkId] === 1) return callback(require);
/******/ if(installedChunks[chunkId] !== undefined)
/******/ installedChunks[chunkId].push(callback);
/******/ else {
/******/ installedChunks[chunkId] = [callback];
/******/ var head = document.getElementsByTagName('head')[0];
/******/ var script = document.createElement('script');
/******/ script.type = 'text/javascript';
/******/ script.src = modules.c+chunkId+modules.a;
/******/ head.appendChild(script);
/******/ }
/******/ };
/******/ window[modules.b] = function(chunkId, moreModules) {
/******/ for(var moduleId in moreModules)
/******/ modules[moduleId] = moreModules[moduleId];
/******/ var callbacks = installedChunks[chunkId];
/******/ installedChunks[chunkId] = 1;
/******/ for(var i = 0; i < callbacks.length; i++)
/******/ callbacks[i](require);
/******/ };
/******/ return require(0);
/******/ }
/******/})(document)

18
lib/templateSingle.js Normal file
View File

@ -0,0 +1,18 @@
/******/(function(document, undefined) {
/******/ return function(modules) {
/******/ var installedModules = {};
/******/ function require(moduleId) {
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId];
/******/ var module = installedModules[moduleId] = {
/******/ exports: {}
/******/ };
/******/ modules[moduleId](module, module.exports, require);
/******/ return module.exports;
/******/ }
/******/ require.ensure = function(chunkId, callback) {
/******/ callback(require);
/******/ };
/******/ return require(0);
/******/ }
/******/})(document)

168
lib/webpack.js Normal file
View File

@ -0,0 +1,168 @@
var buildDeps = require("./buildDeps");
var path = require("path");
var writeChunk = require("./writeChunk");
var fs = require("fs");
var templateAsync = require("fs").readFileSync(path.join(__dirname, "templateAsync.js"));
var templateSingle = require("fs").readFileSync(path.join(__dirname, "templateSingle.js"));
/*
webpack(context, moduleName, options, callback);
webpack(context, moduleName, callback);
webpack(absoluteModulePath, options, callback);
webpack(absoluteModulePath, callback);
callback: function(err, source / stats)
source if options.output is not set
else stats json
options:
- outputJsonpFunction
JSONP function used to load chunks
- scriptSrcPrefix
Path from where chunks are loaded
- outputDirectory
write files to this directory (absolute path)
- output
write first chunk to this file
- outputPostfix
write chunks to files named chunkId plus outputPostfix
- libary
exports of input file are stored in this variable
- minimize
minimize outputs with uglify-js
- includeFilenames
add absolute filenames of input files as comments
*/
module.exports = function(context, moduleName, options, callback) {
if(typeof moduleName === "object") {
callback = options;
options = moduleName;
moduleName = "./" + path.basename(context);
context = path.dirname(context);
}
if(typeof moduleName === "function") {
callback = moduleName;
options = {};
moduleName = "./" + path.basename(context);
context = path.dirname(context);
}
if(!callback) {
callback = options;
options = {};
}
buildDeps(context, moduleName, options, function(err, depTree) {
if(err) {
callback(err);
return;
}
var buffer = [];
if(options.output) {
if(!options.outputJsonpFunction)
options.outputJsonpFunction = "webpackJsonp" + (options.libary || "");
options.scriptSrcPrefix = options.scriptSrcPrefix || "";
if(!options.outputDirectory) {
options.outputDirectory = path.dirname(options.output);
options.output = path.basename(options.output);
}
if(!options.outputPostfix) {
options.outputPostfix = "." + options.output;
}
var fileSizeMap = {};
var chunksCount = 0;
for(var chunkId in depTree.chunks) {
var chunk = depTree.chunks[chunkId];
if(chunk.empty) continue;
if(chunk.equals !== undefined) continue;
chunksCount++;
var filename = path.join(options.outputDirectory,
chunk.id === 0 ? options.output : chunk.id + options.outputPostfix);
buffer = [];
if(chunk.id === 0) {
if(options.libary) {
buffer.push("/******/var ");
buffer.push(options.libary);
buffer.push("=\n");
}
if(Object.keys(depTree.chunks).length > 1) {
buffer.push(templateAsync);
buffer.push("/******/({a:");
buffer.push(stringify(options.outputPostfix));
buffer.push(",b:");
buffer.push(stringify(options.outputJsonpFunction));
buffer.push(",c:");
buffer.push(stringify(options.scriptSrcPrefix));
buffer.push(",\n");
} else {
buffer.push(templateSingle);
buffer.push("/******/({\n");
}
} else {
buffer.push("/******/");
buffer.push(options.outputJsonpFunction);
buffer.push("(");
buffer.push(chunk.id);
buffer.push(", {\n");
}
buffer.push(writeChunk(depTree, chunk, options));
buffer.push("/******/})");
buffer = buffer.join("");
if(options.minimize) buffer = uglify(buffer, filename);
fs.writeFile(filename, buffer, "utf-8", function(err) {
if(err) throw err;
});
fileSizeMap[path.basename(filename)] = buffer.length;
}
buffer = {};
buffer.chunkCount = chunksCount;
buffer.modulesCount = Object.keys(depTree.modulesById).length;
var sum = 0;
for(var chunkId in depTree.chunks) {
for(var moduleId in depTree.chunks[chunkId].modules) {
if(depTree.chunks[chunkId].modules[moduleId] === "include")
sum++;
}
}
buffer.modulesIncludingDuplicates = sum;
buffer.modulesPerChunk = Math.round(sum / chunksCount*10)/10;
sum = 0;
for(var moduleId in depTree.chunks[0].modules) {
if(depTree.chunks[0].modules[moduleId] === "include")
sum++;
}
buffer.modulesFirstChunk = sum;
buffer.fileSizes = fileSizeMap;
callback(null, buffer);
} else {
if(options.libary) {
buffer.push("/******/var ");
buffer.push(options.libary);
buffer.push("=\n");
}
buffer.push(templateSingle);
buffer.push("/******/({\n");
buffer.push(writeChunk(depTree, options));
buffer.push("/******/})");
buffer = buffer.join("");
if(options.minimize) buffer = uglify(buffer, "output");
callback(null, buffer);
}
});
}
function uglify(input, filename) {
var uglify = require("uglify-js");
try {
source = uglify.parser.parse(input);
source = uglify.uglify.ast_mangle(source);
source = uglify.uglify.ast_squeeze(source);
source = uglify.uglify.gen_code(source);
} catch(e) {
console.error(filename + " @ Line " + e.line + ", Col " + e.col + ", " + e.message);
return input;
}
return source;
}
function stringify(str) {
return '"' + str.replace(/\\/g, "\\\\").replace(/\"/g, "\\\"") + '"';
}

28
lib/writeChunk.js Normal file
View File

@ -0,0 +1,28 @@
var writeSource = require("./writeSource");
module.exports = function(depTree, chunk, options) {
if(!options) {
options = chunk;
chunk = null;
}
var buffer = [];
var modules = chunk ? chunk.modules : depTree.modulesById;
for(var moduleId in modules) {
if(chunk) {
if(chunk.modules[moduleId] !== "include")
continue;
}
var module = depTree.modulesById[moduleId];
buffer.push("/******/");
buffer.push(moduleId);
buffer.push(": function(module, exports, require) {\n\n");
if(options.includeFilenames) {
buffer.push("/*** ");
buffer.push(module.filename);
buffer.push(" ***/\n\n");
}
buffer.push(writeSource(module));
buffer.push("\n\n/******/},\n/******/\n");
}
return buffer.join("");
}

46
lib/writeSource.js Normal file
View File

@ -0,0 +1,46 @@
module.exports = function(module) {
var replaces = []; // { from: 123, to: 125, value: "4" }
function genReplaceRequire(requireItem) {
if(requireItem.nameRange && requireItem.id !== undefined) {
replaces.push({
from: requireItem.nameRange[0],
to: requireItem.nameRange[1],
value: "" + requireItem.id
});
}
}
if(module.requires) {
module.requires.forEach(genReplaceRequire);
}
if(module.asyncs) {
module.asyncs.forEach(function genReplacesAsync(asyncItem) {
if(asyncItem.requires) {
asyncItem.requires.forEach(genReplaceRequire);
}
if(asyncItem.asyncs) {
asyncItem.asyncs.forEach(genReplacesAsync);
}
if(asyncItem.namesRange) {
replaces.push({
from: asyncItem.namesRange[0],
to: asyncItem.namesRange[1],
value: ((asyncItem.chunkId || "0") + "")
});
}
});
}
replaces.sort(function(a, b) {
return b.from - a.from;
});
var source = module.source;
var result = [source];
replaces.forEach(function(repl) {
var remSource = result.shift();
result.unshift(
remSource.substr(0, repl.from),
repl.value,
remSource.substr(repl.to+1)
);
});
return result.join("");
}

20
package.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "webpack",
"version": "0.1.0",
"author": "Tobias Koppers @sokra",
"description": "Bundle CommonJS modules into single script or multiple scripts for web browser",
"dependencies": {
"esprima": "0.9.8",
"optimist": "0.2.x",
"uglify-js": "1.2.5"
},
"devDependencies": {
"vows": "*"
},
"engines": {
"node": ">=0.1.30"
},
"main": "lib/webpack.js",
"bin": "./bin/webpack.js",
"licence": "MIT"
}

10
require-polyfill.js Normal file
View File

@ -0,0 +1,10 @@
// Polyfill for node.js
// adds require.ensure
// call it like this: require("webpack/require-polyfill")(require);
module.exports = function(req) {
if(!req.ensure) {
req.ensure = function(array, callback) {
callback(req);
};
}
}

2
require-polyfill.web.js Normal file
View File

@ -0,0 +1,2 @@
// No poly fille needed when compiled with webpack
module.exports = function(){}

28
test/browsertest/build.js Normal file
View File

@ -0,0 +1,28 @@
var cp = require('child_process');
var argv = process.argv;
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) {
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) {
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) {
console.log('web stdout:\n' + stdout);
console.log('web stderr:\n ' + stderr);
if (error !== null) {
console.log('web error: ' + error);
}
});

View File

@ -0,0 +1 @@
module.exports = "a";

View File

@ -0,0 +1,4 @@
require.ensure(["./acircular2"], function(require) {
require("./acircular2")
window.test(true, "Circular async loading 1")
})

View File

@ -0,0 +1,4 @@
require.ensure(["./acircular"], function(require) {
require("./acircular")
window.test(true, "Circular async loading 2")
})

View File

@ -0,0 +1 @@
module.exports = require("./a");

View File

@ -0,0 +1,2 @@
module.exports = 1;
module.exports = require("./circular");

View File

@ -0,0 +1,2 @@
module.exports = 2;
module.exports = require("./circular2");

View File

@ -0,0 +1,3 @@
require.ensure(["./a"], function(require) {
window.test(require("./a") === "a", "Duplicate module should work")
})

View File

@ -0,0 +1,3 @@
require.ensure(["./b"], function(require) {
window.test(require("./b") === "a", "Duplicate indirect module should work")
})

View File

@ -0,0 +1 @@
window.test(false, "index.js should be replaced with index.web.js");

View File

@ -0,0 +1,43 @@
window.test(true, "index.js should be replaced with index.web.js");
window.test(window.libary1, "libary1 loaded");
window.test(window.libary2.ok, "libary2 loaded");
require.ensure("subcontent", function(require) {
// Comments work!
exports.ok = true;
window.test(require("subcontent") === "replaced", "node_modules should be replaced with web_modules");
window.test(require("subcontent2/file.js") === "orginal", "node_modules should still work when web_modules exists");
});
setTimeout(function() {
window.test(exports.ok, "asnyc loaded, exports is still avaible");
}, 3000);
window.test(require("./circular") === 1, "circular require should work");
window.test(require("./singluar.js").value === 1, "sigular module loaded");
require("./singluar.js").value = 2;
window.test(require("./singluar").value === 2, "exported object is singluar");
window.test(require("subfilemodule") === "subfilemodule", "Modules as single file should load");
require.ensure([], function(require) {
require("./acircular");
require("./duplicate");
require("./duplicate2");
});
require.ensure([], function(require) {
require("./acircular2");
require("./duplicate");
require("./duplicate2");
});
var sum = 0;
require.ensure([], function(require) {
require("./duplicate");
require("./duplicate2");
sum++;
});
require.ensure([], function(require) {
require("./duplicate");
require("./duplicate2");
sum++;
});
setTimeout(function() {
window.test(sum === 2, "Multiple callbacks on code load finish");
}, 3000);

View File

@ -0,0 +1 @@
module.exports.value = 1;

13
test/browsertest/node_modules/libary1/index.js generated vendored Normal file
View File

@ -0,0 +1,13 @@
// Single File Libary
window.test(window.writing, "Lib1 Should be in first tick");
window.test(require("./lib/component") === "lib1 component", "Lib1 component loaded");
require.ensure(["submodule1", "submodule2"], function(require) {
window.test(window.writing, "Lib1 Should be in first tick too");
window.test(require("submodule1") === "submodule1", "Lib1 submodule1 loaded");
window.test(require("submodule2") === "submodule2", "Lib1 submodule2 loaded");
window.test(require("submodule3")() === "submodule3", "Lib1 submodule3 loaded");
require.ensure([], function(require) {
window.test(window.writing, "Lib1 Should be still in first tick");
});
});
module.exports = true;

1
test/browsertest/node_modules/libary1/lib/comp.js generated vendored Normal file
View File

@ -0,0 +1 @@
module.exports = "lib1 component";

View File

@ -0,0 +1 @@
module.exports = require("./comp.js");

View File

@ -0,0 +1 @@
module.exports = "submodule1";

View File

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

1
test/browsertest/node_modules/libary2/lib/extra.js generated vendored Normal file
View File

@ -0,0 +1 @@
module.exports = "Lib2 extra";

21
test/browsertest/node_modules/libary2/lib/main.js generated vendored Normal file
View File

@ -0,0 +1,21 @@
// Chunked File Libary
window.test(window.writing, "Lib2 Should be in first tick");
var asnycOk = false, asnycOk2 = false;
require.ensure("./extra", function(require) {
asnycOk = true;
window.test(!window.writing, "Lib2 Should be asynchron loaded");
var sameTick = true;
require.ensure([], function(require) {
asnycOk2 = true;
window.test(require("./extra") === "Lib2 extra", "Lib2 extra loaded");
window.test(sameTick, "Lib2 Should be in the same tick, as it is a empty chunk");
});
sameTick = false;
});
setTimeout(function() {
window.test(asnycOk, "Lib2 Chunk 1 should be loaded");
window.test(asnycOk2, "Lib2 Chunk 2 should be loaded");
}, 3000);
window.test(!asnycOk, "Lib2 Chunk 1 should not be loaded yet");
window.test(!asnycOk2, "Lib2 Chunk 2 should not be loaded yet");
exports.ok = true;

View File

@ -0,0 +1 @@
module.exports = "submodule1";

View File

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

View File

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

3
test/browsertest/node_modules/libary2/package.json generated vendored Normal file
View File

@ -0,0 +1,3 @@
{
"main": "lib/main.js"
}

2
test/browsertest/node_modules/subcontent/index.js generated vendored Normal file
View File

@ -0,0 +1,2 @@
window.test(false, "This file should not be loaded, because it is replaced");
modules.exports = "error";

1
test/browsertest/node_modules/subcontent2/file.js generated vendored Normal file
View File

@ -0,0 +1 @@
module.exports = "orginal";

1
test/browsertest/node_modules/subfilemodule.js generated vendored Normal file
View File

@ -0,0 +1 @@
module.exports = "subfilemodule";

3
test/browsertest/node_modules/submodule3/index.js generated vendored Normal file
View File

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

View File

@ -0,0 +1,29 @@
<html>
<head>
<title>webpack tests</title>
<script>
window.test = function(ok, message) {
document.write("<p>" + (ok?"OK: ":"FAILED: ")+message.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + "</p>");
};
window.onerror = function(error) {
window.test(false, "Shouldn't throw error: " + error);
};
window.writing = true;
</script>
<script src="js/libary1.js"></script>
<script src="js/libary2.js"></script>
<script src="js/web.js"></script>
<script>
window.test = function(ok, message) {
var p = document.createElement("p");
p.appendChild(document.createTextNode((ok?"OK: ":"FAILED: ")+message.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")));
document.body.appendChild(p);
};
window.writing = false;
</script>
</head>
<body>
</body>
</html>

View File

@ -0,0 +1,2 @@
window.test(true, "This file should be loaded, because it is the replacement");
module.exports = "replaced";

79
test/buildDeps_test.js Normal file
View File

@ -0,0 +1,79 @@
var vows = require("vows");
var assert = require("assert");
var path = require("path");
var buildDeps = require("../lib/buildDeps");
vows.describe("buildDeps").addBatch({
"main1": {
topic: function() {
buildDeps(path.join(__dirname, "fixtures"), "./main1.js", this.callback);
},
"all modules loaded": function(depTree) {
assert.notEqual(depTree.modules[path.join(__dirname, "fixtures", "main1.js")], null);
assert.notEqual(depTree.modules[path.join(__dirname, "fixtures", "a.js")], null);
assert.notEqual(depTree.modules[path.join(__dirname, "fixtures", "b.js")], null);
assert.notEqual(depTree.modules[path.join(__dirname, "fixtures", "node_modules", "m1", "a.js")], null);
},
"one chunk": function(depTree) {
assert.deepEqual(Object.keys(depTree.chunks), ["0"]);
for(var i in depTree.modulesById) {
assert.deepEqual(depTree.modulesById[i].chunks, [0]);
}
}
},
"main2": {
topic: function() {
buildDeps(path.join(__dirname, "fixtures"), "./main2.js", {}, this.callback);
},
"all modules loaded": function(depTree) {
assert.notEqual(depTree.modules[path.join(__dirname, "fixtures", "main2.js")], null);
assert.notEqual(depTree.modules[path.join(__dirname, "fixtures", "a.js")], null);
assert.notEqual(depTree.modules[path.join(__dirname, "fixtures", "b.js")], null);
assert.notEqual(depTree.modules[path.join(__dirname, "fixtures", "node_modules", "m1", "a.js")], null);
assert.notEqual(depTree.modules[path.join(__dirname, "fixtures", "node_modules", "m1", "b.js")], null);
},
"two chunks": function(depTree) {
assert.deepEqual(Object.keys(depTree.chunks), ["0", "1"]);
assert.deepEqual(depTree.modules[path.join(__dirname, "fixtures", "main2.js")].chunks, [0]);
assert.deepEqual(depTree.modules[path.join(__dirname, "fixtures", "a.js")].chunks, [0]);
assert.deepEqual(depTree.modules[path.join(__dirname, "fixtures", "b.js")].chunks, [0]);
assert.deepEqual(depTree.modules[path.join(__dirname, "fixtures", "node_modules", "m1", "a.js")].chunks, [1]);
assert.deepEqual(depTree.modules[path.join(__dirname, "fixtures", "node_modules", "m1", "b.js")].chunks, [1]);
}
},
"main3": {
topic: function() {
buildDeps(path.join(__dirname, "fixtures"), "./main3.js", {}, this.callback);
},
"all modules loaded": function(depTree) {
assert.notEqual(depTree.modules[path.join(__dirname, "fixtures", "main3.js")], null);
assert.notEqual(depTree.modules[path.join(__dirname, "fixtures", "a.js")], null);
assert.notEqual(depTree.modules[path.join(__dirname, "fixtures", "c.js")], null);
},
"two chunks": function(depTree) {
assert.deepEqual(Object.keys(depTree.chunks), ["0", "1"]);
assert.deepEqual(depTree.modules[path.join(__dirname, "fixtures", "main3.js")].chunks, [0]);
assert.deepEqual(depTree.modules[path.join(__dirname, "fixtures", "a.js")].chunks, [0, 1]);
assert.deepEqual(depTree.modules[path.join(__dirname, "fixtures", "c.js")].chunks, [1]);
var main3id = ""+depTree.modules[path.join(__dirname, "fixtures", "main3.js")].id;
var aid = ""+depTree.modules[path.join(__dirname, "fixtures", "a.js")].id;
var cid = ""+depTree.modules[path.join(__dirname, "fixtures", "c.js")].id;
assert.deepEqual(Object.keys(depTree.chunks[0].modules), [main3id, aid]);
assert.deepEqual(Object.keys(depTree.chunks[1].modules), [cid, aid]);
assert.deepEqual(depTree.chunks[0].modules[main3id], "include");
assert.deepEqual(depTree.chunks[0].modules[aid], "include");
assert.deepEqual(depTree.chunks[1].modules[aid], "in-parent");
assert.deepEqual(depTree.chunks[1].modules[cid], "include");
}
}
}).export(module);

3
test/fixtures/a.js vendored Normal file
View File

@ -0,0 +1,3 @@
module.exports = function a() {
return "This is a";
};

3
test/fixtures/b.js vendored Normal file
View File

@ -0,0 +1,3 @@
module.exports = function b() {
return "This is b";
};

4
test/fixtures/c.js vendored Normal file
View File

@ -0,0 +1,4 @@
module.exports = function b() {
require("./a");
return "This is c";
};

13
test/fixtures/complex.js vendored Normal file
View File

@ -0,0 +1,13 @@
var complex1 = require("./lib/complex1");
require.ensure(["./lib/complex1", "complexm/step2"], function(require) {
require("./lib/complex1");
var a = function() {}
require.ensure(["complexm/step1"], function(require) {
require("./lib/complex1");
var s1 = require("complexm/step1");
var s2 = require("complexm/step2");
console.log(s1);
console.log(s2);
});
});
console.log(complex1);

1
test/fixtures/lib/complex1.js vendored Normal file
View File

@ -0,0 +1 @@
module.exports = "lib complex1";

10
test/fixtures/main1.js vendored Normal file
View File

@ -0,0 +1,10 @@
var a = require("./a");
if(x) {
for(var i = 0; i < 100; i++) {
while(true)
require("./b");
do {
i++;
} while(require("m1/a")());
}
}

12
test/fixtures/main2.js vendored Normal file
View File

@ -0,0 +1,12 @@
var a = require("./a");
with(x) {
switch(a) {
case 1:
require("./b");
default:
require.ensure(["m1/a"], function() {
var a = require("m1/a"),
b = require("m1/b");
});
}
}

4
test/fixtures/main3.js vendored Normal file
View File

@ -0,0 +1,4 @@
var a = require("./a");
require.ensure([], function(require) {
require("./c.js");
});

1
test/fixtures/node_modules/complexm/step1.js generated vendored Normal file
View File

@ -0,0 +1 @@
module.exports = require("m1/a") + require("m1");

1
test/fixtures/node_modules/complexm/step2.js generated vendored Normal file
View File

@ -0,0 +1 @@
module.exports = "Step2";

View File

@ -0,0 +1 @@
module.exports = "the correct a.js";

View File

@ -0,0 +1 @@
module.exports = " :) " + require("m2/b.js");

3
test/fixtures/node_modules/m1/a.js generated vendored Normal file
View File

@ -0,0 +1,3 @@
module.exports = function a() {
return "This is m1/a";
};

3
test/fixtures/node_modules/m1/b.js generated vendored Normal file
View File

@ -0,0 +1,3 @@
module.exports = function a() {
return "This is m1/b";
};

1
test/fixtures/node_modules/m2/b.js generated vendored Normal file
View File

@ -0,0 +1 @@
module.exports = "This is m2/b";

31
test/resolve_test.js Normal file
View File

@ -0,0 +1,31 @@
var vows = require("vows");
var assert = require("assert");
var path = require("path");
var resolve = require("../lib/resolve");
var fixtures = path.join(__dirname, "fixtures");
function testResolve(context, moduleName, result) {
return {
topic: function() {
resolve(context, moduleName, {}, this.callback);
},
"correct filename": function(filename) {
assert.equal(filename, result);
}
}
}
vows.describe("resolve").addBatch({
"resolve simple 1": testResolve(fixtures, "./main1.js", path.join(fixtures, "main1.js")),
"resolve simple 2": testResolve(fixtures, "./main1", path.join(fixtures, "main1.js")),
"resolve simple 3": testResolve(fixtures, "./a.js", path.join(fixtures, "a.js")),
"resolve simple 4": testResolve(fixtures, "./a", path.join(fixtures, "a.js")),
"resolve module 1": testResolve(fixtures, "m1/a.js", path.join(fixtures, "node_modules", "m1", "a.js")),
"resolve module 2": testResolve(fixtures, "m1/a", path.join(fixtures, "node_modules", "m1", "a.js")),
"resolve complex 1": testResolve(fixtures, "complexm/step1", path.join(fixtures, "node_modules", "complexm", "step1.js")),
"resolve complex 2": testResolve(path.join(fixtures, "node_modules", "complexm", "web_modules", "m1"),
"m2/b.js", path.join(fixtures, "node_modules", "m2", "b.js")),
}).export(module);