Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Paul Baumgart
committed
Mar 27, 2012
0 parents
commit b9d6ecb
Showing
23 changed files
with
782 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
node_modules/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
Copyright (C) 2012 ProxV, Inc. | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
# jsbundle -- Node.JS modules for the browser | ||
|
||
**jsbundle** takes your Node modules and makes them work in the browser. | ||
|
||
It finds all *require* calls in your code and includes the necessary module files. Then, it wraps all these modules using the Node variant of CommonJS module headers. | ||
|
||
It handles *node\_modules* directories and *package.json* files just like Node does. | ||
|
||
It comes with a "Dev CDN" that will serve the latest *jsbundled* version of your code via local HTTP. | ||
|
||
## Usage | ||
|
||
### jsbundle | ||
|
||
jsbundle <entry_file> [config_file] | ||
|
||
Bundle up *entry\_file* and all its dependencies, optionally using configuration specified in *config\_file*, and write it to *stdout*. | ||
|
||
This command is basically equivalent to running: | ||
|
||
node <entry_file> | ||
|
||
in the sense that when the resulting script is executed in the browser, the module *entry\_file* will be the first module to begin executing. | ||
|
||
For production deployment, you'll probably want to pipe the resulting output to the JavaScript minifier of your choice. | ||
|
||
### devcdn | ||
|
||
devcdn [config_file] [--port tcp_port] | ||
|
||
Start a "Dev CDN", serving on *tcp\_port* (default 8081), optionally using configuration specified in *config\_file*. | ||
The *entry\_file* passed to *jsbundle* is determined by the request URL, which will be resolved relative to the current working directory of *devcdn*. | ||
|
||
## Tests | ||
|
||
Test coverage is currently mediocre. You can run tests with: | ||
|
||
npm test | ||
|
||
## Caveats | ||
|
||
* All values passed to *require* in your code must be string literals. Otherwise, *jsbundle* wouldn't be able to reliably find all the modules to include in the bundled output. | ||
|
||
* The special variable *\_\_filename* is equal to the *module.id*. If you specify the *mangleNames* option (see below), then the *\_\_filename* will be the mangled numeric id of the module. | ||
|
||
* The special variable *\_\_dirname* doesn't really make sense in the context of browser modules, so while the variable exists, its value is *undefined*. | ||
|
||
## Config | ||
|
||
### Example | ||
|
||
{ | ||
"outputFilters": [ | ||
"./ext/logger-filter" | ||
], | ||
"extraRequires": { | ||
"_": "underscore", | ||
"Logger": "./ext/logger" | ||
}, | ||
"beforeModuleBody": [ | ||
"var logger = new Logger(__filename);" | ||
], | ||
"afterModuleBody": [], | ||
"mangleNames": true, | ||
"logLevel": "off" | ||
} | ||
|
||
All configuration is optional. If you want to configure *jsbundle* operation, create a JSON file with one or more of the following key/value pairs: | ||
|
||
### mangleNames | ||
By default, *jsbundle* uses the absolute path of a file as its module id. This is useful for development, but in production it's wasteful and potentially reveals information you want to keep private. If you enable the **mangleNames** option, module ids will be numeric instead. | ||
|
||
### extraRequires | ||
You can specify additional requires that *jsbundle* will automatically add to all of your modules. This is useful for e.g. ensuring you always have underscore available without having to pollute the global namespace or remember to manually require it every time. The value for this configuration option must be an object literal, with keys the variable name for the required module and values the path to the module. **Relative paths will be resolved relative to the config file location.** | ||
|
||
### beforeModuleBody | ||
An array of arbitrary JavaScript statements to insert **before** every module body. | ||
|
||
### afterModuleBody | ||
An array of arbitrary JavaScript statements to insert **after** every module body. | ||
|
||
### outputFilters | ||
An array of output filters module files, resolved relative to the config file path. | ||
|
||
Output filters allow you to specify additional ways to transform your module code. They are regular Node modules that must export an *init* function. This function takes in the *jsbundle* configuration object as a parameter and returns a second function. The returned function must accept a string containing the source code of a module and return the transformed source code, also as a string. | ||
|
||
Example: | ||
|
||
exports.init = function(config) { | ||
return function(sourceCode) { | ||
return sourceCode + '; alert("this is a silly output filter");'; | ||
}; | ||
}; | ||
|
||
[Here is a more useful example.](https://github.com/proxv/jsbundle/blob/master/ext/logger-filter.js) | ||
|
||
## Thanks To | ||
|
||
* [substack](https://github.com/substack) for his [browserify](https://github.com/substack/node-browserify) package, which served as inspiration for *jsbundle*. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#!/usr/bin/env node | ||
|
||
var argv = require('optimist').argv; | ||
var jsbundle = require('../jsbundle'); | ||
|
||
jsbundle.devCdn(argv.port || 8081, argv._[0] || argv.config); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
#!/usr/bin/env node | ||
|
||
var argv = require('optimist').argv; | ||
var jsbundle = require('../jsbundle'); | ||
|
||
if (argv._.length > 1) { | ||
throw new Error('only one entry file can be specified at a time'); | ||
} | ||
|
||
require('util').puts(jsbundle.bundle(argv._[0], argv.config)); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"outputFilters": [ | ||
"./ext/logger-filter" | ||
], | ||
"extraRequires": { | ||
"_": "underscore", | ||
"Logger": "./ext/logger" | ||
}, | ||
"beforeModuleBody": [ | ||
"var logger = new Logger(__filename);" | ||
], | ||
"afterModuleBody": [], | ||
"mangleNames": true, | ||
"logLevel": "off" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
var parser = require('uglify-js').parser; | ||
var uglify = require('uglify-js').uglify; | ||
var _ = require('underscore'); | ||
|
||
var LOG_LEVELS = { | ||
'off': 0, | ||
'error': 1, | ||
'warn': 2, | ||
'info': 3, | ||
'debug': 4, | ||
'trace': 5 | ||
}; | ||
|
||
function init(options) { | ||
var logLevel = options.logLevel; | ||
if (typeof logLevel !== 'string' || typeof LOG_LEVELS[logLevel] === 'undefined') { | ||
throw new Error('invalid log level: ' + logLevel); | ||
} | ||
|
||
var allowedLogLevelsSet = {}; | ||
|
||
_(LOG_LEVELS).each(function(rank, name) { | ||
if (rank > 0 && rank <= LOG_LEVELS[logLevel]) { | ||
allowedLogLevelsSet[name] = true; | ||
} | ||
}); | ||
|
||
function loggerFilter(src) { | ||
var replacementOffset = 0; | ||
var walker = uglify.ast_walker(); | ||
|
||
walker.with_walkers({ | ||
'call': function() { | ||
if (this[1][0] === 'dot' && | ||
this[1][1][0] === 'name' && | ||
this[1][1][1] === 'logger') { | ||
var logLevel = this[1][2]; | ||
if (allowedLogLevelsSet[logLevel] !== true) { | ||
var token = this[0]; | ||
var startPos = token.start.pos + replacementOffset; | ||
var endPos = token.end.endpos + replacementOffset; | ||
src = src.substring(0, startPos) + '/* ' + | ||
src.substring(startPos, endPos) + ' */' + | ||
src.substring(endPos); | ||
replacementOffset += '/* */'.length; | ||
} | ||
} | ||
} | ||
}, function() { | ||
return walker.walk(parser.parse(src, false, true)); | ||
}); | ||
|
||
return src; | ||
} | ||
|
||
return loggerFilter; | ||
} | ||
|
||
exports.init = init; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
function Logger(name) { | ||
this._name = name; | ||
} | ||
|
||
var logMethods = ['error', 'warn', 'info', 'debug', 'trace']; | ||
|
||
for (var index = 0, len = logMethods.length; index < len; ++index) { | ||
(function(index, funcName) { | ||
Logger.prototype[funcName] = function() { | ||
var con = self.console; | ||
var message = [funcName[0], '[', this._name, ']'].join(''); | ||
var args = Array.prototype.slice.call(arguments); | ||
var stack_traces = []; | ||
|
||
// logger.trace -> console.log | ||
funcName = funcName === 'trace' ? 'log' : funcName; | ||
|
||
if (!con) { | ||
return; | ||
} | ||
|
||
if (typeof args[0] === 'string') { | ||
message += ': ' + args.shift(); | ||
} | ||
|
||
for (var i = 0, len = args.length; i < len; ++i) { | ||
if (args[i] && args[i].stack) { | ||
stack_traces.push(args[i].stack); | ||
} | ||
} | ||
|
||
if (con.firebug) { | ||
args.unshift(message); | ||
con[funcName].apply(self, args); | ||
} else { | ||
if (args.length <= 0) { | ||
con[funcName] ? con[funcName](message) : | ||
con.log(message); | ||
} else if (args.length === 1) { | ||
con[funcName] ? con[funcName](message, args[0]) : | ||
con.log(message, args[0]); | ||
} else { | ||
con[funcName] ? con[funcName](message, args) : | ||
con.log(message, args); | ||
} | ||
} | ||
|
||
var len = stack_traces.length; | ||
|
||
if (len > 0) { | ||
con.log('Listing exception stack traces individually:'); | ||
for (var i = 0; i < len; ++i) { | ||
con.log(stack_traces[i]); // why? because in Google Chrome, | ||
// this will make clickable links | ||
} | ||
} | ||
}; | ||
})(index, logMethods[index]); | ||
} | ||
|
||
module.exports = Logger; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
var bundle = require('./lib/bundle'); | ||
var devCdn = require('./lib/dev-cdn'); | ||
|
||
exports.bundle = bundle; | ||
exports.devCdn = devCdn; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
var crypto = require('crypto'); | ||
var fs = require('fs'); | ||
var path = require('path'); | ||
var _ = require('underscore'); | ||
var template = require('./template'); | ||
var Module = require('./module'); | ||
|
||
function _expandExtraRequires(extraRequires, relativeToDir) { | ||
var expandedRequires = {}; | ||
_(extraRequires).each(function (value, key) { | ||
if (typeof value === 'string' && /^\.\/|^\.\./.test(value)) { | ||
value = path.resolve(relativeToDir, value); | ||
} | ||
expandedRequires[key] = value; | ||
}); | ||
return expandedRequires; | ||
} | ||
|
||
function _compileStatementArray(statementArray) { | ||
var compiledStatements = []; | ||
_(statementArray).each(function (value) { | ||
compiledStatements.push(/;\s*$/.test(value) ? value : value + ';'); | ||
}); | ||
return compiledStatements.join('\n'); | ||
} | ||
|
||
function _buildModuleOptions(options, relativeToDir) { | ||
var moduleOptions = {}; | ||
|
||
moduleOptions.extraRequires = _expandExtraRequires(options.extraRequires, relativeToDir); | ||
moduleOptions.beforeModuleBody = _compileStatementArray(options.beforeModuleBody); | ||
moduleOptions.afterModuleBody = _compileStatementArray(options.afterModuleBody); | ||
|
||
return moduleOptions; | ||
} | ||
|
||
function _initOutputFilters(options, relativeToDir) { | ||
var outputFilters = []; | ||
_(options.outputFilters).each(function(outputFilterPath) { | ||
outputFilterPath = path.resolve(relativeToDir, outputFilterPath); | ||
var outputFilter = require(outputFilterPath).init(options); | ||
outputFilters.push(outputFilter); | ||
}); | ||
return outputFilters; | ||
} | ||
|
||
function bundle(entryFile, configFile) { | ||
entryFile = path.resolve(entryFile); | ||
|
||
var configDir; | ||
var options = {}; | ||
var counter = 0; | ||
var files = [ entryFile ]; | ||
|
||
var modules = {}; | ||
var moduleHashToIdMap = {}; | ||
var moduleFileToIdMap = {}; | ||
|
||
// for ensuring that extra requires don't also require themselves | ||
var moduleFilesWithExtraRequires = {}; | ||
moduleFilesWithExtraRequires[entryFile] = true; | ||
var moduleIdsWithExtraRequires = {}; | ||
|
||
if (configFile) { | ||
options = JSON.parse(fs.readFileSync(configFile, 'utf-8')); | ||
configDir = path.dirname(configFile); | ||
} | ||
var moduleOptions = _buildModuleOptions(options, configDir); | ||
moduleOptions.outputFilters = _initOutputFilters(options, configDir); | ||
|
||
while (files.length > 0) { | ||
var file = path.resolve(files.shift()); | ||
var id = moduleFileToIdMap[file]; | ||
if (!id) { | ||
var mod = new Module(file, moduleOptions); | ||
|
||
// resolve duplicates: identical files with different paths | ||
// this comes up a lot because of how npm copies modules everywhere | ||
var hash = mod.sha1(); | ||
id = moduleHashToIdMap[hash]; | ||
if (!id) { | ||
id = moduleHashToIdMap[hash] = (options.mangleNames ? counter++ : file); | ||
} | ||
|
||
modules[id] = mod; | ||
moduleFileToIdMap[file] = id; | ||
|
||
var deps = mod.dependencies(); | ||
|
||
// only "real" dependencies, not "extra" dependencies, get registered | ||
// as needing extra requires | ||
if (moduleFilesWithExtraRequires[file] === true) { | ||
_(deps).each(function(dep) { | ||
moduleFilesWithExtraRequires[dep] = true; | ||
}); | ||
} | ||
|
||
files = files.concat(mod.extraDependencies().concat(deps)); | ||
} | ||
|
||
if (moduleFilesWithExtraRequires[file] === true) { | ||
moduleIdsWithExtraRequires[id] = true; | ||
} | ||
} | ||
|
||
var moduleDefs = []; | ||
_(modules).each(function(module, id) { | ||
module.updateRequires(moduleFileToIdMap, moduleIdsWithExtraRequires[id] !== true); | ||
module.setId(id); | ||
moduleDefs.push(module.compile()); | ||
}); | ||
|
||
return template.compile('bundle', { | ||
moduleDefs: moduleDefs.join('\n\n'), | ||
mainModuleId: JSON.stringify(moduleFileToIdMap[path.resolve(entryFile)]) | ||
}); | ||
} | ||
|
||
module.exports = bundle; | ||
|
Oops, something went wrong.