Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

initial commit

  • Loading branch information...
commit b9d6ecbebb2a4ea42561220c1e8ae234306039a1 0 parents
@paulbaumgart paulbaumgart authored
1  .gitignore
@@ -0,0 +1 @@
+node_modules/
8 LICENSE
@@ -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.
+
100 README.md
@@ -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*.
+
7 bin/devcdn
@@ -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);
+
11 bin/jsbundle
@@ -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));
+
15 config.json
@@ -0,0 +1,15 @@
+{
+ "outputFilters": [
+ "./ext/logger-filter"
+ ],
+ "extraRequires": {
+ "_": "underscore",
+ "Logger": "./ext/logger"
+ },
+ "beforeModuleBody": [
+ "var logger = new Logger(__filename);"
+ ],
+ "afterModuleBody": [],
+ "mangleNames": true,
+ "logLevel": "off"
+}
60 ext/logger-filter.js
@@ -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;
+
62 ext/logger.js
@@ -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;
+
6 jsbundle.js
@@ -0,0 +1,6 @@
+var bundle = require('./lib/bundle');
+var devCdn = require('./lib/dev-cdn');
+
+exports.bundle = bundle;
+exports.devCdn = devCdn;
+
120 lib/bundle.js
@@ -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;
+
46 lib/dev-cdn.js
@@ -0,0 +1,46 @@
+var connect = require('connect');
+var path = require('path');
+var bundle = require('./bundle');
+
+module.exports = function(port, configFile) {
+ var devCdn = connect().use(
+ function(req, resp, next) {
+ if (req.url === '/favicon.ico') {
+ resp.writeHead(404);
+ resp.end();
+ return;
+ }
+
+ var filename = path.resolve(req.url.replace(/^\/|\?.+$/, ''));
+ if (filename.substr(0, process.cwd().length + 1) !== (process.cwd() + '/')) {
+ resp.writeHead(403, { 'Content-Type': 'text/plain' });
+ resp.end();
+ return;
+ }
+
+ try {
+ var bundled = bundle(filename, configFile);
+ resp.writeHead(200, {
+ 'Content-Type': 'application/javascript',
+ 'Cache-Control': 'no-cache'
+ });
+ resp.write(bundled);
+ console.log('DevCdn served file: ' + filename);
+ } catch (e) {
+ resp.writeHead(500, { 'Content-Type': 'text/plain' });
+ resp.write(e.stack);
+ console.log(e.stack);
+ }
+ resp.end();
+ },
+
+ function(req, resp, next) {
+ logger.info('DevCdn file not found: ' + req.url);
+ }
+ );
+
+ console.log('DevCdn server starting on port ' + port);
+ devCdn.listen(port);
+ return devCdn;
+};
+
181 lib/module.js
@@ -0,0 +1,181 @@
+var moduleHandler = require('module');
+var crypto = require('crypto');
+var path = require('path');
+var fs = require('fs');
+var parser = require('uglify-js').parser;
+var uglify = require('uglify-js').uglify;
+var _ = require('underscore');
+var template = require('./template');
+
+function Module(filename, options) {
+ this._filename = path.resolve(filename);
+ this._options = options || {};
+ this._extraDependencies = [];
+ this._extraRequires = this._compileExtraRequires(this._options.extraRequires);
+ this._load();
+}
+
+Module.prototype = {
+ _representationForModuleHandler: function() {
+ return {
+ filename: this._filename,
+ id: this._filename,
+ paths: moduleHandler._nodeModulePaths(path.dirname(this._filename))
+ };
+ },
+
+ _resolve: function(requiredValue) {
+ return moduleHandler._resolveFilename(requiredValue, this._representationForModuleHandler())[1];
+ },
+
+ _formatParseError: function(message, astNode) {
+ var lineNumber = astNode[0] && astNode[0].start && (astNode[0].start.line + 1);
+ return message + ': ' + uglify.gen_code(astNode) + ' (' + this._filename + (lineNumber ? ':' + lineNumber : '') + ')';
+ },
+
+ _parse: function(src) {
+ try {
+ return parser.parse(src, false, true);
+ } catch (e) {
+ var lines = src.split(/\n|\r\n/).slice(e.line - 3, e.line + 3);
+ var line = lines.join('\n');
+ throw new Error(e.message + ":\n\n" + (line ? line : "") + "\n\n(" + this._filename + ")");
+ }
+ },
+
+ _compileExtraRequires: function(extraRequires) {
+ var itself = this;
+ var compiledRequires = [];
+ _(extraRequires).each(function (value, key) {
+ value = itself._resolve(value);
+ itself._extraDependencies.push(value);
+ compiledRequires.push('var ' + key + ' = require(' + JSON.stringify(value) + ');');
+ });
+ return compiledRequires.join('\n');
+ },
+
+ _findRequireCalls: function(src) {
+ var itself = this;
+ var walker = uglify.ast_walker();
+ var requireCalls = [];
+
+ walker.with_walkers({
+ 'call': function() {
+ if (uglify.gen_code(this[1]) === 'require') {
+ var paramToken = this[2][0][0];
+ if (paramToken.start.type === 'string') {
+ var resolvedName = itself._resolve(paramToken.start.value);
+ if (resolvedName.charAt(0) !== '/') {
+ throw new Error(itself._formatParseError("can't handle native module require", this))
+ }
+ requireCalls.push({ token: paramToken, resolvedName: resolvedName });
+ } else {
+ throw new Error(itself._formatParseError("can't handle non-const require", this));
+ }
+ }
+ }
+ }, function() {
+ return walker.walk(itself._parse(src));
+ });
+
+ return requireCalls;
+ },
+
+ _load: function() {
+ this._rawSrc = fs.readFileSync(this._filename, 'utf-8');
+
+ // strip shebang
+ this._rawSrc = this._rawSrc.replace(/^\s*#![^\n]*/, '');
+
+ this._moduleDef = null;
+ this._id = null;
+ },
+
+ dependencies: function() {
+ var dependencySet = {};
+ _(this._findRequireCalls(this._rawSrc)).each(function(requireCall) {
+ dependencySet[requireCall.resolvedName] = true;
+ });
+
+ return _(dependencySet).keys();
+ },
+
+ extraDependencies: function() {
+ return this._extraDependencies;
+ },
+
+ _wrapModule: function(ignoreOptions) {
+ var src = this._rawSrc;
+
+ // turn JSON files into valid CommonJS modules
+ if (path.extname(this._filename) === 'json') {
+ src = 'module.exports = ' + src + ';';
+ }
+
+ var templateData = {};
+
+ if (ignoreOptions) {
+ templateData.extraRequires = '';
+ templateData.beforeModuleBody = '';
+ templateData.afterModuleBody = '';
+ } else {
+ _(templateData).extend(this._options);
+ templateData.extraRequires = this._extraRequires;
+ }
+
+ _(templateData).extend({
+ body: src,
+ filename: this._filename
+ });
+
+ this._moduleDef = template.compile('module-wrapper', templateData);
+ },
+
+ updateRequires: function(idMap, ignoreOptions) {
+ this._wrapModule(ignoreOptions);
+
+ var replacementOffset = 0;
+ var itself = this;
+
+ _(this._findRequireCalls(this._moduleDef)).each(function(requireCall) {
+ var token = requireCall.token;
+ // look up id if an idMap was passed in, else use the resolvedName
+ var id = JSON.stringify(idMap ? idMap[requireCall.resolvedName] : requireCall.resolvedName);
+ var startPos = token.start.pos + replacementOffset;
+ var endPos = token.end.endpos + replacementOffset;
+ itself._moduleDef = itself._moduleDef.substring(0, startPos) +
+ id +
+ itself._moduleDef.substring(endPos);
+ replacementOffset += id.length - (endPos - startPos);
+ });
+ },
+
+ setId: function(id) {
+ this._id = id;
+ },
+
+ compile: function() {
+ var src = template.compile('module-prefix', { id: JSON.stringify(this._id), moduleDef: this._moduleDef });
+
+ _(this._options.outputFilters).each(function(filterFn) {
+ src = filterFn(src);
+ if (typeof src !== 'string') {
+ throw new Error('output filter did not return string:\n' + filterFn.toString);
+ }
+ });
+
+ return src;
+ },
+
+ sha1: function() {
+ var hash = crypto.createHash('sha1');
+ return hash.update(this._rawSrc, 'utf-8').digest('hex');
+ },
+
+ filename: function() {
+ return this._filename;
+ }
+};
+
+module.exports = Module;
+
18 lib/template.js
@@ -0,0 +1,18 @@
+var fs = require('fs');
+var _ = require('underscore');
+
+function compile(templateName, data) {
+ data = data || {};
+ var filename = __dirname + '/../tmpl/' + templateName + '.tmpl';
+ var template = fs.readFileSync(filename, 'utf-8');
+ return template.replace(/@(\w+)/g, function(ignored, key) {
+ if (data[key] !== void 0) {
+ return data[key];
+ } else {
+ throw new Error("missing template data: '" + key + "'");
+ }
+ });
+}
+
+exports.compile = compile;
+
26 package.json
@@ -0,0 +1,26 @@
+{
+ "author": "ProxV, Inc.",
+ "name": "jsbundle",
+ "description": "Bundle your modules into a single file for the browser.",
+ "version": "0.1.0",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/proxv/jsbundle.git"
+ },
+ "main": "jsbundle.js",
+ "scripts": {
+ "test": "find test -name \"test*.js\" | xargs node_modules/vows/bin/vows"
+ },
+ "dependencies": {
+ "uglify-js": "1.x.x",
+ "underscore": "1.x.x",
+ "optimist": "0.3.x",
+ "connect": "2.x.x"
+ },
+ "devDependencies": {
+ "vows": "0.6.x"
+ },
+ "engines": {
+ "node": "0.6.x"
+ }
+}
17 test/fixtures/abc.js
@@ -0,0 +1,17 @@
+#!/usr/bin/env node
+
+exports.hi = function() {
+ logger.warn(
+ 'derp'
+ );
+}
+
+var def = require(
+ './def\
+.js'
+);
+
+alert(def());
+
+require('./ghi.js');
+
5 test/fixtures/config.json
@@ -0,0 +1,5 @@
+{
+ "extraRequires": {
+ "_": "underscore"
+ }
+}
3  test/fixtures/def.js
@@ -0,0 +1,3 @@
+module.exports = function() {
+ return 'def';
+};
0  test/fixtures/ghi.js
No changes.
18 test/test-bundle.js
@@ -0,0 +1,18 @@
+var vows = require('vows');
+var assert = require('assert');
+var path = require('path');
+var bundle = require('../lib/bundle');
+
+vows.describe('module resolution').addBatch({
+ "basic": {
+ topic: bundle(__dirname + '/fixtures/abc.js', __dirname + '/fixtures/config.json'),
+
+ "find all modules": function(bundled) {
+ assert.match(bundled, /moduleFns\["[^"]+abc.js"\]/);
+ assert.match(bundled, /moduleFns\["[^"]+def.js"\]/);
+ assert.match(bundled, /moduleFns\["[^"]+ghi.js"\]/);
+ assert.match(bundled, /moduleFns\["[^"]+underscore.js"\]/);
+ }
+ }
+}).export(module);
+
38 test/test-module.js
@@ -0,0 +1,38 @@
+var vows = require('vows');
+var assert = require('assert');
+var path = require('path');
+var Module = require('../lib/module');
+
+vows.describe('test Module').addBatch({
+ "Module": {
+ topic: new Module(__dirname + '/fixtures/abc.js', {
+ extraRequires: {
+ '_': 'underscore'
+ },
+ beforeModuleBody: '',
+ afterModuleBody: ''
+ }),
+
+ "dependencies / extraDependencies": function(mod) {
+ var deps = mod.dependencies().sort();
+ var extraDeps = mod.extraDependencies().sort();
+ assert.equal(deps.length, 2);
+ assert.equal(extraDeps.length, 1);
+ assert.match(deps[0], /def\.js$/);
+ assert.match(extraDeps[0], /underscore.js$/);
+ },
+
+ "updateRequires": function(mod) {
+ mod.updateRequires();
+ var src = mod._moduleDef;
+ assert.match(src, /underscore.js"\s*\)/);
+ assert.match(src, /def.js"\s*\)/);
+ },
+
+ "setId / compile": function(mod) {
+ mod.setId(123);
+ assert.match(mod.compile(), /moduleFns\[123\]/);
+ }
+ }
+}).export(module);
+
27 tmpl/bundle.tmpl
@@ -0,0 +1,27 @@
+(function() {
+ var _$_moduleFns = {};
+ var _$_modules = {};
+
+ function _$_require(id) {
+ var moduleFn = _$_moduleFns[id];
+ if (moduleFn) {
+ var mod = _$_modules[id];
+ if (!mod) {
+ mod = _$_modules[id] = {
+ id: id,
+ exports: {}
+ };
+ mod.main = _$_modules[@mainModuleId];
+ moduleFn(mod, mod.exports, _$_require, id);
+ }
+ return mod.exports;
+ } else {
+ throw new Error("Cannot find module '" + id + "'");
+ }
+ }
+
+ @moduleDefs
+
+ _$_require(@mainModuleId);
+})();
+
1  tmpl/module-prefix.tmpl
@@ -0,0 +1 @@
+_$_moduleFns[@id] = @moduleDef
12 tmpl/module-wrapper.tmpl
@@ -0,0 +1,12 @@
+(function(module, exports, require, __filename, __dirname) {
+
+@extraRequires
+
+@beforeModuleBody
+
+// @filename
+@body
+
+@afterModuleBody
+
+});
Please sign in to comment.
Something went wrong with that request. Please try again.