├── .gitignore ├── LICENSE ├── README.markdown ├── TODO.markdown ├── assets ├── modulr.sync.js └── modulr.sync.resolved.js ├── lib ├── abstract-collector.js ├── ast-collector.js ├── builder.js ├── collector.js ├── dev-collector.js ├── json-fs.js ├── logger.js └── resolved-ast-collector.js ├── main.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Your choice of license: MIT or Apache, Version 2.0. 2 | 3 | MIT License 4 | =========== 5 | 6 | Copyright (c) 2010 Tobie Langel (http://tobielangel.com) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | Apache License, Version 2.0 27 | =========================== 28 | 29 | Copyright 2010 Tobie Langel 30 | 31 | Licensed under the Apache License, Version 2.0 (the "License"); 32 | you may not use this file except in compliance with the License. 33 | You may obtain a copy of the License at 34 | 35 | http://www.apache.org/licenses/LICENSE-2.0 36 | 37 | Unless required by applicable law or agreed to in writing, software 38 | distributed under the License is distributed on an "AS IS" BASIS, 39 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 40 | See the License for the specific language governing permissions and 41 | limitations under the License. -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | ***THIS PROJECT IS NO LONGER MAINTAINED*** 2 | 3 | modulr 4 | ====== 5 | 6 | Resolves and concatenates [CommonJS module][1] dependencies for use in the browser. It's a port of [`modulr`][2] from Ruby to [node.js][3] and is based on [`module-grapher`][4], a node module which resolves dependencies through recursive static analysis. 7 | 8 | Install 9 | ------- 10 | 11 | `modulr` is available as an NPM module. 12 | 13 | $ npm install modulr 14 | 15 | Usage 16 | ----- 17 | 18 | `modulr` accepts the main module's identifier and an optional config object as arguments which get passed to `module-grapher`. It returns a result object whose `output` property is a string containing [a small runtime][5] and the concatenated modules sources. Optionally, this output can be minified and the module identifiers resolved server-side. 19 | 20 | ```javascript 21 | require('modulr').build('foo', { 22 | paths: ['./lib', './vendor'], // defaults to ['.'] 23 | root: 'path/to/package/root/' // defaults to process.cwd() 24 | minify: true, // defaults to false 25 | resolveIdentifiers: true, // defaults to false 26 | minifyIdentifiers: false, // defaults to false 27 | environment: 'prod' // or 'dev', defaults to undefined 28 | }, callback); 29 | 30 | // Dump the output to `main.js`. 31 | function callback (err, result) { 32 | if (err) { throw err; } 33 | require('fs').writeFileSync('/path/to/main.js', result.output, 'utf8'); 34 | } 35 | ``` 36 | 37 | `modulr` can also accepts a [CommonJS package][6] or its `package.json` file as argument. In which case it uses the JSON file's `main` value as entry point, the package's dir as root, and picks the rest of its options from the JSON file's `modulr` namespace. 38 | 39 | ```javascript 40 | require('modulr').buildFromPackage('path/to/package', callback); 41 | ``` 42 | 43 | Development Environments 44 | ------------------------ 45 | 46 | `modulr` provides a development environment. It is enabled by setting the config option `environment` to `"dev"`: 47 | 48 | ```javascript 49 | require('modulr').build('foo', { environment: 'dev' }, callback); 50 | ``` 51 | This does essentially two things. 52 | 53 | 1. It sets the global variable `__DEV__` to `true`. This allows adding development-only code (e.g. logging) that is completely stripped out of production builds, e.g.: 54 | ```javascript 55 | if (__DEV__) { console.log('Module Foo loaded.'); } 56 | ``` 57 | 58 | 2. It adds [`sourceURL` comments](http://blog.getfirebug.com/2009/08/11/give-your-eval-a-name-with-sourceurl/) to each modules. Rendering engines that support these (at least Gecko and WebKit) will give original file names and line numbers to thrown errors even though all modules are packaged in a single file. 59 | 60 | Minification 61 | ------------ 62 | 63 | `modulr` uses [Uglify](https://github.com/mishoo/UglifyJS/) to optionally minify the output. To enable minification, set the `minify` config option to `true`. To also minify module identifiers, set the `minifyIdentifiers` option to `true`. Note that minification is not compatible with the `"dev"` environment. 64 | 65 | ```javascript 66 | require('modulr').build('foo', { minify: true }, callback); 67 | ``` 68 | 69 | Lazy evaluation 70 | --------------- 71 | 72 | [Lazy evaluation](http://calendar.perfplanet.com/2011/lazy-evaluation-of-commonjs-modules/) is a technique which allows delaying parsing and evaluation of modules until they are needed (for example, following a user action) while keeping a synchronous programming model. 73 | 74 | To lazy eval modules, pass a list of absolute module IDs in the configuration object. 75 | 76 | ```javascript 77 | require('modulr').build('foo', { 78 | lazyEval: ['path/to/module/bar', 'path/to/baz'] 79 | }, callback); 80 | ``` 81 | 82 | or in the `package.json` file: 83 | 84 | ```javascript 85 | { 86 | "modulr": { 87 | "lazyEval": ["path/to/bar", "path/to/baz"] 88 | } 89 | } 90 | ``` 91 | 92 | Resolving identifiers at build time 93 | ----------------------------------- 94 | 95 | CommonJS module identifiers can be absolute or relative. Relative identifiers are simplify development but have an extra runtime cost: the path to the module's identifier has to be calculated every time the module is required, and a context aware require function has to be created for every module. 96 | 97 | In order to avoid that extra cost, `modulr` is able to resolve identifiers at build time which produces modified builds which only contain absolute identifiers and uses a [lighter runtime](https://github.com/tobie/modulr-node/blob/master/assets/modulr.sync.resolved.js). To enable this option, set the `resolveIdentifiers` config option to `true`: 98 | 99 | ```javascript 100 | require('modulr').build('foo', { resolveIdentifiers: true }, callback); 101 | ``` 102 | 103 | Instrumenting Performance 104 | ------------------------- 105 | 106 | As applications become increasingly complex, startup time tends to suffer. While `modulr` helps mitigate this through optimizations such as resolving identifiers at build time or lazy evaluation, it's sometimes useful to be able to do some serious auditing and find out which modules slow down startup time. 107 | 108 | That's what `modulr`'s instrumentPerformance config option enables. Turn it on like so: 109 | 110 | ```javascript 111 | require('modulr').build('foo', { instrumentPerformance: true }, callback); 112 | ``` 113 | 114 | This adds a slew of data to the `modulr.perf` object available in the global scope (for example, through the console). This data is of the form: 115 | 116 | ```javascript 117 | { 118 | "start": 1334878573462, // All times are in ms. 119 | "defineStart": 1334878573462, // 120 | "defineEnd": 1334878573464, // 121 | "requireMainStart": 1334878573464, // 122 | "modules": { // Object containing all defined modules. 123 | "foo": { // 124 | "count": 0 // Module "foo" has not been required yet. 125 | }, // 126 | // 127 | "main": { // Module "main" has been required once. 128 | "count": 1, // Evaluation of that module and it's 129 | "left": 1, // dependencies took 16 ms. 130 | "start": 1334878573464, // Notice the use of nested sets to 131 | "right": 4, // store initialization order. 132 | "end": 1334878573480 // 133 | }, // 134 | // 135 | "bar": { // Module "bar" has been required twice. 136 | "count": 2, // It was lazy evaluated. 137 | "left": 2, // 138 | "start": 1334878573466, // 139 | "evalStart": 1334878573466, // Lazy evaluation took 12 ms. 140 | "evalEnd": 1334878573478, // 141 | "right": 3, // 142 | "end": 1334878573480 // 143 | } // 144 | }, // 145 | "requireMainEnd": 1334878573480, // 146 | "end": 1334878573480 // 147 | } 148 | ``` 149 | 150 | To visualize this data, just copy and paste it (or in modern browsers, simply drag and drop the JSON file) onto [this page](http://tobie.github.com/modulr-node/perf.html). You'll get a beautiful waterfall chart of your application's initialization stage thanks to a little bit of [d3.js](http://mbostock.github.com/d3/) magic. 151 | 152 | License 153 | ------- 154 | 155 | Your choice of [MIT or Apache, Version 2.0 licenses][7]. `modulr` is copyright 2010 [Tobie Langel][8]. 156 | 157 | [1]: http://wiki.commonjs.org/wiki/Modules/1.1 158 | [2]: https://github.com/tobie/modulr 159 | [3]: http://nodejs.org 160 | [4]: https://github.com/tobie/module-grapher 161 | [5]: https://github.com/tobie/modulr-node/blob/master/assets/modulr.sync.js 162 | [6]: http://wiki.commonjs.org/wiki/Packages/1.1 163 | [7]: https://raw.github.com/tobie/modulr-node/master/LICENSE 164 | [8]: http://tobielangel.com 165 | 166 | -------------------------------------------------------------------------------- /TODO.markdown: -------------------------------------------------------------------------------- 1 | - allow third party runtimes 2 | - allow async loading 3 | - build a connect middleware for dev purposes 4 | - instrument runtime (ids and number of calls define and require, load time, etc.) 5 | - localStorage-caching mode. 6 | -------------------------------------------------------------------------------- /assets/modulr.sync.js: -------------------------------------------------------------------------------- 1 | // modulr.sync.js (c) 2010 Tobie Langel 2 | (function(exports) { 3 | var modulr = {}, 4 | _factories = {}, 5 | _modules = {}, 6 | PREFIX = '__module__', // Poor man's hasOwnProperty 7 | RELATIVE_IDENTIFIER_PATTERN = /^\.\.?\//; 8 | 9 | if (__PERF__) { 10 | var _perf = modulr.perf = { 11 | start: Date.now(), 12 | modules: {} 13 | }, 14 | _pos = 1; 15 | } 16 | 17 | function makeRequire(id, main) { 18 | // Find the requirer's dirname from it's id. 19 | var path = id.substring(0, id.lastIndexOf('/') + 1); 20 | 21 | function require(identifier) { 22 | if (__PERF__) { var t0 = Date.now(); } 23 | var id = resolveIdentifier(identifier, path), 24 | key = PREFIX + id, 25 | mod = _modules[key]; 26 | 27 | if (__PERF__) { 28 | var _p = _perf.modules[id]; 29 | _p.count++; 30 | } 31 | // Check if this module's factory has already been called. 32 | if (!mod) { 33 | if (__PERF__) { 34 | _p.left = _pos++; 35 | _p.start = t0; 36 | } 37 | var fn = _factories[key]; 38 | delete _factories[key]; // no longer needed. 39 | 40 | if (!fn) { throw 'Can\'t find module "' + identifier + '".'; } 41 | 42 | // lazy eval 43 | if (typeof fn === 'string') { 44 | if (__PERF__) { _p.evalStart = Date.now(); } 45 | fn = new Function('require', 'exports', 'module', fn); 46 | if (__PERF__) { _p.evalEnd = Date.now(); } 47 | } 48 | 49 | _modules[key] = mod = { id: id, exports: {} }; 50 | // Create an instance of `require` per module. Each instance has a 51 | // reference to the path it was called from to be able to properly 52 | // resolve relative identifiers. 53 | // `main` isn't defined until we actually require the program's 54 | // entry point. 55 | var r = makeRequire(id, main || mod); 56 | fn.call(exports, r, mod.exports, mod); 57 | if (__PERF__) { 58 | _p.right = _pos++; 59 | _p.end = Date.now(); 60 | } 61 | } 62 | return mod.exports; 63 | } 64 | 65 | require.main = main; 66 | return require; 67 | } 68 | 69 | function resolveIdentifier(identifier, dir) { 70 | var parts, part, path; 71 | 72 | if (!RELATIVE_IDENTIFIER_PATTERN.test(identifier)) { 73 | return identifier; 74 | } 75 | 76 | parts = (dir + identifier).split('/'); 77 | 78 | path = []; 79 | for (var i = 0, length = parts.length; i < length; i++) { 80 | part = parts[i]; 81 | switch (part) { 82 | case '': 83 | case '.': 84 | continue; 85 | case '..': 86 | if (path.length) { 87 | path.pop(); 88 | } else { 89 | throw new RangeError('Out of bounds identifier: ' + identifier); 90 | } 91 | break; 92 | default: 93 | path.push(part); 94 | } 95 | } 96 | return path.join('/'); 97 | } 98 | 99 | function define(id, factory) { 100 | if (__PERF__) { _perf.modules[id] = { count: 0 }; } 101 | _factories[PREFIX + id] = factory; 102 | } 103 | 104 | exports.define = define; 105 | exports.require = makeRequire(''); 106 | exports.modulr = modulr; 107 | })(this); 108 | 109 | if (__PERF__) { modulr.perf.defineStart = Date.now(); } 110 | -------------------------------------------------------------------------------- /assets/modulr.sync.resolved.js: -------------------------------------------------------------------------------- 1 | // modulr.sync.js (c) 2010 Tobie Langel 2 | (function(exports) { 3 | var modulr = {}, 4 | _factories = {}, 5 | _modules = {}, 6 | PREFIX = '__module__'; // Poor man's hasOwnProperty 7 | 8 | if (__PERF__) { 9 | var _perf = modulr.perf = { 10 | start: Date.now(), 11 | modules: {} 12 | }, 13 | _pos = 1; 14 | } 15 | 16 | function require(id) { 17 | var key = PREFIX + id, 18 | mod = _modules[key]; 19 | 20 | if (__PERF__) { 21 | var _p = _perf.modules[id]; 22 | _p.count++; 23 | } 24 | 25 | if (mod) { return mod.exports; } 26 | 27 | if (__PERF__) { 28 | _p.left = _pos++; 29 | _p.start = Date.now(); 30 | } 31 | 32 | var fn = _factories[key]; 33 | delete _factories[key]; 34 | 35 | if (!fn) { throw 'Can\'t find module "' + id + '".'; } 36 | 37 | // lazy eval 38 | if (typeof fn === 'string') { 39 | if (__PERF__) { _p.evalStart = Date.now(); } 40 | fn = new Function('require', 'exports', 'module', fn); 41 | if (__PERF__) { _p.evalEnd = Date.now(); } 42 | } 43 | 44 | _modules[key] = mod = { id: id, exports: {} }; 45 | // require.main isn't defined until we actually require the program's 46 | // entry point. 47 | if (!require.main) { require.main = mod; } 48 | fn.call(exports, require, mod.exports, mod); 49 | if (__PERF__) { 50 | _p.right = _pos++; 51 | _p.end = Date.now(); 52 | } 53 | return mod.exports; 54 | } 55 | 56 | function define(id, factory) { 57 | if (__PERF__) { _perf.modules[id] = { count: 0 }; } 58 | _factories[PREFIX + id] = factory; 59 | } 60 | 61 | exports.define = define; 62 | exports.require = require; 63 | exports.modulr = modulr; 64 | })(this); 65 | 66 | if (__PERF__) { modulr.perf.defineStart = Date.now(); } 67 | -------------------------------------------------------------------------------- /lib/abstract-collector.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | path = require('path'); 3 | 4 | exports.AbstractCollector = AbstractCollector; 5 | function AbstractCollector(config) { 6 | this.config = config; 7 | } 8 | 9 | (function(p) { 10 | p._main = null; 11 | p._modules = null; 12 | p._lazyEvaluatedModules = null; 13 | p._constants = null; 14 | 15 | p.setConstants = setConstants; 16 | function setConstants(constants) { 17 | this._constants = constants; 18 | } 19 | 20 | p.setModules = setModules; 21 | function setModules(modules) { 22 | this._modules = modules; 23 | } 24 | 25 | p.setLazyEvaluatedModules = setLazyEvaluatedModules; 26 | function setLazyEvaluatedModules(modules) { 27 | this._lazyEvaluatedModules = modules; 28 | } 29 | 30 | p.addMainModule = addMainModule; 31 | function addMainModule(m) { 32 | this._main = m; 33 | } 34 | 35 | p.getConstants = getConstants; 36 | function getConstants() { 37 | return (this._constants = this._constants || {}); 38 | } 39 | 40 | p.getModules = getModules; 41 | function getModules() { 42 | return (this._modules = this._modules || {}); 43 | } 44 | 45 | p.getLazyEvaluatedModules = getLazyEvaluatedModules; 46 | function getLazyEvaluatedModules() { 47 | return (this._lazyEvaluatedModules = this._lazyEvaluatedModules || {}); 48 | } 49 | 50 | p.render = render; 51 | function render(buffer) { 52 | var modules = this.getModules(); 53 | 54 | buffer.push(this.renderRuntime()); 55 | 56 | // convert to array for sorting. 57 | var arr = []; 58 | for (id in modules) { 59 | arr.push(modules[id]); 60 | } 61 | 62 | arr.sort(function(a, b) { 63 | var delta = a.getSize() - b.getSize(); 64 | if (delta !== 0) { return delta; } 65 | return a.id > b.id ? 1 : -1; 66 | }); 67 | 68 | var lazyEval = this.getLazyEvaluatedModules(); 69 | arr.forEach(function(m) { 70 | if (m.duplicateOf) { 71 | // would be pointless to lazy eval these modules. 72 | buffer.push(this.defineModule(m)); 73 | } else if (m.id in lazyEval) { 74 | buffer.push(this.defineLazyEvaluatedModule(m)); 75 | } else { 76 | buffer.push(this.defineModule(m)); 77 | } 78 | }, this); 79 | 80 | buffer.push(this.beforeRequireCall()); 81 | buffer.push(this.renderRequireCall(this._main)); 82 | buffer.push(this.afterRequireCall()); 83 | } 84 | })(AbstractCollector.prototype); 85 | 86 | exports.getRuntimeSrcCode = getRuntimeSrcCode; 87 | function getRuntimeSrcCode(filename) { 88 | var p = path.join(__dirname, '..', 'assets', filename); 89 | return fs.readFileSync(p, 'utf8'); 90 | } -------------------------------------------------------------------------------- /lib/ast-collector.js: -------------------------------------------------------------------------------- 1 | var util = require('util'), 2 | abstractCollector = require('./abstract-collector'), 3 | SuperClass = abstractCollector.AbstractCollector, 4 | _super = SuperClass.prototype, 5 | uglify = require('uglify-js'), 6 | processor = uglify.uglify, 7 | parser = uglify.parser; 8 | 9 | var RUNTIME = getRuntimeAst('modulr.sync.js'); 10 | 11 | exports.getRuntimeAst = getRuntimeAst; 12 | function getRuntimeAst(filename) { 13 | var src = abstractCollector.getRuntimeSrcCode(filename); 14 | return toAstBody(parser.parse(src)); 15 | } 16 | 17 | exports.createAstCollector = createAstCollector; 18 | exports.create = createAstCollector; 19 | function createAstCollector(config) { 20 | return new AstCollector(config); 21 | } 22 | 23 | exports.AstCollector = AstCollector; 24 | function AstCollector(config) { 25 | SuperClass.call(this, config); 26 | } 27 | 28 | util.inherits(AstCollector, SuperClass); 29 | 30 | (function(p) { 31 | p.getModuleIdAst = getModuleIdAst; 32 | function getModuleIdAst(m) { 33 | return ["string", m.id]; 34 | } 35 | 36 | p.encloseModule = encloseModule; 37 | function encloseModule(m) { 38 | var ast = this.getModuleAst(m); 39 | return ["function", null, ["require", "exports", "module"], toAstBody(ast)]; 40 | } 41 | 42 | p.defineModule = defineModule; 43 | function defineModule(m) { 44 | var args = [this.getModuleIdAst(m), this.encloseModule(m)]; 45 | return getFunctionCallStatementAst("define", args); 46 | } 47 | 48 | p.defineLazyEvaluatedModule = defineLazyEvaluatedModule; 49 | function defineLazyEvaluatedModule(m) { 50 | var ast = this.getModuleAst(m), 51 | args = [this.getModuleIdAst(m), ["string", this.generateCode(ast)]]; 52 | 53 | return getFunctionCallStatementAst("define", args); 54 | } 55 | 56 | p.beforeRequireCall = beforeRequireCall; 57 | function beforeRequireCall() { 58 | var ast = parser.parse('if (__PERF__) { modulr.perf.defineEnd = modulr.perf.requireMainStart = Date.now(); }'); 59 | return toAstBody(ast); 60 | } 61 | 62 | p.renderRequireCall = renderRequireCall; 63 | function renderRequireCall(m) { 64 | return getFunctionCallStatementAst("require", [this.getModuleIdAst(m)]); 65 | } 66 | 67 | p.afterRequireCall = afterRequireCall; 68 | function afterRequireCall() { 69 | var ast = parser.parse('if (__PERF__) { modulr.perf.requireMainEnd = modulr.perf.end = Date.now(); }'); 70 | return toAstBody(ast); 71 | } 72 | 73 | p.renderRuntime = renderRuntime; 74 | function renderRuntime() { 75 | return RUNTIME; 76 | } 77 | 78 | p.generateCode = generateCode; 79 | function generateCode(ast) { 80 | if (this.config.minify) { 81 | ast = toAstRoot(ast); 82 | ast = processor.ast_mangle(ast, { 83 | toplevel: false, 84 | except: [], 85 | defines: this.getConstantsAsAstExpressions() 86 | }); 87 | ast = processor.ast_squeeze(ast); 88 | } else { 89 | ast = toAstBody(ast); 90 | var constants = this.getConstants(); 91 | Object.keys(constants).sort().reverse().forEach(function(k) { 92 | ast.unshift(getVarAssignmentAst(k, constants[k])); 93 | }); 94 | ast = toAstRoot(ast); 95 | } 96 | return processor.gen_code(ast, { 97 | inline_script: this.config.inlineSafe, 98 | beautify: !this.config.minify 99 | }); 100 | } 101 | 102 | p.getConstantsAsAstExpressions = getConstantsAsAstExpressions; 103 | function getConstantsAsAstExpressions() { 104 | var constants = this.getConstants(), 105 | output = {}; 106 | for (var k in constants) { 107 | output[k] = expressionToAst(constants[k]); 108 | } 109 | return output; 110 | } 111 | 112 | p.getModuleAst = getModuleAst; 113 | function getModuleAst(m) { 114 | var ast; 115 | if (m.duplicateOf) { 116 | ast = ["stat", 117 | ["assign", true, 118 | ["dot", ["name", "module"], "exports"], 119 | getFunctionCallAst("require", [this.getModuleIdAst(m.duplicateOf)]) 120 | ] 121 | ]; 122 | } else { 123 | ast = m.ast; 124 | } 125 | return ast; 126 | } 127 | 128 | p.toString = toString; 129 | function toString() { 130 | var buffer = createAstBuffer(); 131 | this.render(buffer); 132 | return this.generateCode(buffer.ast); 133 | } 134 | 135 | })(AstCollector.prototype); 136 | 137 | exports.expressionToAst = expressionToAst; 138 | function expressionToAst(exp) { 139 | var ast = parser.parse('(' + JSON.stringify(exp) + ')'); 140 | return toAstBody(ast)[0][1]; 141 | } 142 | 143 | exports.getVarAssignmentAst = getVarAssignmentAst; 144 | function getVarAssignmentAst(identifier, value) { 145 | var ast = parser.parse('var ' + identifier + ' = ' + JSON.stringify(value) + ';'); 146 | return toAstBody(ast)[0]; 147 | } 148 | 149 | exports.getFunctionCallAst = getFunctionCallAst; 150 | function getFunctionCallAst(name, args) { 151 | return ["call", ["name", name], args]; 152 | } 153 | 154 | exports.getFunctionCallStatementAst = getFunctionCallStatementAst; 155 | function getFunctionCallStatementAst(name, args) { 156 | return ["stat", getFunctionCallAst(name, args)]; 157 | } 158 | 159 | exports.toAstBody = toAstBody; 160 | function toAstBody(ast) { 161 | // A body of an AST is a plain array. It can be used 162 | // both as the body of a function and the body of a 163 | // program. 164 | if (isAstBody(ast)) { 165 | // We're all good. This is already a body. 166 | return ast; 167 | } 168 | 169 | if (isAstRoot(ast)) { 170 | // We're dealing with a root. Get it's body. 171 | return ast[1]; 172 | } 173 | // Lower level than a body. Convert it to a body by 174 | // wrapping it into an array. 175 | return [ast]; 176 | } 177 | 178 | exports.toAstRoot = toAstRoot; 179 | function toAstRoot(ast) { 180 | if (isAstRoot(ast)) { 181 | // Already an AST root. 182 | return ast; 183 | } 184 | if (isAstBody(ast)) { 185 | // This is a body, wrap it up in a root node. 186 | return ['toplevel', ast]; 187 | } 188 | // Lower level than a body. More wrapping up. 189 | return ['toplevel', [ast]]; 190 | } 191 | 192 | exports.isAstRoot = isAstRoot; 193 | function isAstRoot(ast) { 194 | return ast[0] === 'toplevel'; 195 | } 196 | 197 | exports.isAstBody = isAstBody; 198 | function isAstBody(ast) { 199 | // AST bodies are the only anonymous nodes. 200 | return typeof ast[0] === 'object'; 201 | } 202 | 203 | exports.createAstBuffer = createAstBuffer; 204 | function createAstBuffer() { 205 | return new AstBuffer(); 206 | } 207 | 208 | exports.AstBuffer = AstBuffer; 209 | function AstBuffer() { 210 | this.ast = parser.parse(''); 211 | this.body = toAstBody(this.ast); 212 | } 213 | 214 | (function(p) { 215 | p.push = push; 216 | function push(items) { 217 | var body = this.body; 218 | body.push.apply(body, toAstBody(items)); 219 | } 220 | })(AstBuffer.prototype); -------------------------------------------------------------------------------- /lib/builder.js: -------------------------------------------------------------------------------- 1 | var DEV = 'dev', 2 | PROD = 'prod'; 3 | 4 | 5 | exports.createBuilder = createBuilder; 6 | exports.create = createBuilder; 7 | function createBuilder(config) { 8 | return new Builder(config); 9 | } 10 | 11 | exports.Builder = Builder; 12 | function Builder(config) { 13 | this.config = config; 14 | } 15 | 16 | (function(p) { 17 | p.build = build; 18 | function build(result) { 19 | var deps = result.dependencies, 20 | lazyEval = null, 21 | collector = this.createCollector(this.config); 22 | 23 | if (this.config.lazyEval === true) { 24 | // if lazy-eval is true, all modules are lazy-evaled. 25 | lazyEval = deps; 26 | } else if (this.config.lazyEval) { 27 | // Else `this.config.lazyEval` is an array of modules ids that 28 | // are to be lazy-evaled. Convert it to id/module object 29 | // pairs. 30 | var ids = this.config.lazyEval, 31 | modules = {}; 32 | 33 | for (var i = 0; i < ids.length; i++) { 34 | var id = ids[i], 35 | module = deps[id]; 36 | 37 | if (module) { 38 | modules[id] = module; 39 | } else { 40 | var msg = 'LazyEval config option only accepts modules which are dependencies of "'; 41 | msg += result.main + '". "' + id + '" is not.'; 42 | throw new TypeError(msg); 43 | } 44 | } 45 | 46 | // Find their dependencies. 47 | lazyEval = this.findScopedDependencies(modules); 48 | } 49 | 50 | // Clone constants 51 | var constants = this.config.constants, 52 | clonedConsts = {}; 53 | 54 | for (var k in constants) { 55 | clonedConsts[k] = constants[k]; 56 | } 57 | 58 | this.setDevConstant(clonedConsts); 59 | this.setPerfConstant(clonedConsts); 60 | 61 | result.lazyEval = lazyEval; 62 | collector.setLazyEvaluatedModules(lazyEval); 63 | collector.setModules(deps); 64 | collector.addMainModule(result.main); 65 | collector.setConstants(clonedConsts); 66 | return collector.toString(); 67 | } 68 | 69 | p.setDevConstant = setDevConstant; 70 | function setDevConstant(constants) { 71 | var env = this.getEnv(), 72 | isDev = env === DEV; 73 | 74 | if (env === null) { return; } 75 | 76 | if ('__DEV__' in constants && constants.__DEV__ !== isDev) { 77 | var msg = 'Contradictory __DEV__ constant (' + JSON.stringify(constants.__DEV__) + ') '; 78 | msg += 'and config.environment (' + JSON.stringify(this.config.environment) + ') values.\n' 79 | msg += 'When using config.environment, avoid also setting __DEV__ constants.' 80 | throw new Error(msg); 81 | } 82 | 83 | constants.__DEV__ = isDev; 84 | } 85 | 86 | p.setPerfConstant = setPerfConstant; 87 | function setPerfConstant(constants) { 88 | constants.__PERF__ = this.config.instrumentPerformance ? true : false; 89 | } 90 | 91 | p.getEnv = function() { 92 | var config = this.config, 93 | env = config.environment; 94 | 95 | if (!('environment' in config)) { 96 | return null; 97 | } 98 | 99 | switch (env) { 100 | case DEV: 101 | case '__DEV__': 102 | case 'development': 103 | return DEV; 104 | break; 105 | case PROD: 106 | case '__PROD__': 107 | case 'production': 108 | return PROD; 109 | break; 110 | default: 111 | throw new TypeError('Unsupported environment: ' + env); 112 | } 113 | } 114 | 115 | p.createCollector = createCollector; 116 | function createCollector(config) { 117 | var collector; 118 | if (config.resolveIdentifiers) { 119 | collector = './resolved-ast-collector'; 120 | } else if (config.minify) { 121 | collector = './ast-collector'; 122 | } else if (this.getEnv() === DEV) { 123 | collector = './dev-collector'; 124 | } else { 125 | collector = './collector'; 126 | } 127 | return require(collector).create(config); 128 | } 129 | 130 | p.findScopedDependencies = findScopedDependencies; 131 | function findScopedDependencies(modules) { 132 | var output = {}; 133 | 134 | // Collect all the modules which are selected for 135 | // lazy evaluation along with their dependencies. 136 | for (var id in modules) { 137 | var module = modules[id], 138 | deps = module.getDependencies(); 139 | 140 | output[id] = module; 141 | for (var k in deps) { output[k] = deps[k]; } 142 | } 143 | 144 | // Loop over the collected modules. Eliminate those that were 145 | // not directly specified (i.e. not members of `modules`) 146 | // and which are required by modules which themselves aren't 147 | // members of `modules`. 148 | var modified = true; 149 | while (modified) { 150 | modified = false; 151 | for (var id in output) { 152 | var reqs = output[id].getRequirers(); 153 | for (var k in reqs) { 154 | if (!(k in output) && !(id in modules)) { 155 | modified = true; 156 | delete output[id]; 157 | } 158 | } 159 | } 160 | } 161 | 162 | return output; 163 | } 164 | })(Builder.prototype); 165 | -------------------------------------------------------------------------------- /lib/collector.js: -------------------------------------------------------------------------------- 1 | var util = require('util'), 2 | abstractCollector = require('./abstract-collector'), 3 | SuperClass = abstractCollector.AbstractCollector, 4 | _super = SuperClass.prototype; 5 | 6 | var JS_ESCAPE_REGEXP = /\\|\r?\n|"/g, 7 | INLINE_SCRIPT_SAFE_JS_ESCAPE_REGEXP = /\\|\r?\n|"|<\//g, 8 | JS_ESCAPE_MAP = { 9 | '\\': '\\\\', 10 | '\n': '\\n', 11 | '\r\n': '\\n', 12 | '"': '\\"', 13 | ' _KiB) { 34 | if (size > 10 * _KiB) { 35 | displaySize = Math.round(size / _KiB); 36 | } else { 37 | displaySize = Math.round(10 * size / _KiB) / 10; 38 | } 39 | suffix = 'K'; 40 | } 41 | if (size > _MiB) { 42 | if (size > 10 * _MiB) { 43 | displaySize = Math.round(size / _MiB); 44 | } else { 45 | displaySize = Math.round(10 * size / _MiB) / 10; 46 | } 47 | suffix = 'M'; 48 | } 49 | return displaySize + suffix; 50 | } 51 | 52 | var _KLOC = 1000; 53 | var _MLOC = 1000 * 1000; 54 | function locString(loc) { 55 | var displayLoc = loc; 56 | var suffix = ''; 57 | if (loc > _KLOC) { 58 | if (loc > 10 * _KLOC) { 59 | displayLoc = Math.round(loc / _KLOC); 60 | } else { 61 | displayLoc = Math.round(10 * loc / _KLOC) / 10; 62 | } 63 | suffix = 'K'; 64 | } 65 | if (loc > _MLOC) { 66 | if (loc > 10 * _MLOC) { 67 | displayLoc = Math.round(loc / _MLOC); 68 | } else { 69 | displayLoc = Math.round(10 * loc / _MLOC) / 10; 70 | } 71 | suffix = 'M'; 72 | } 73 | return displayLoc + suffix; 74 | } 75 | -------------------------------------------------------------------------------- /lib/resolved-ast-collector.js: -------------------------------------------------------------------------------- 1 | var util = require('util'), 2 | astCollector = require('./ast-collector'), 3 | SuperClass = astCollector.AstCollector, 4 | _super = SuperClass.prototype, 5 | uglify = require('uglify-js'), 6 | walker = uglify.uglify.ast_walker(), 7 | identifier = require('module-grapher/lib/identifier'); 8 | 9 | var uniqueId = 0; 10 | var map = {}; 11 | function generateId(id) { 12 | return (map[id] ? map[id] : (map[id] = (uniqueId++).toString(36))); 13 | } 14 | 15 | var RUNTIME = astCollector.getRuntimeAst('modulr.sync.resolved.js'); 16 | 17 | exports.createResolvedAstCollector = createResolvedAstCollector; 18 | exports.create = createResolvedAstCollector; 19 | function createResolvedAstCollector(config) { 20 | return new ResolvedAstCollector(config); 21 | } 22 | 23 | exports.ResolvedAstCollector = ResolvedAstCollector; 24 | function ResolvedAstCollector(config) { 25 | SuperClass.call(this, config); 26 | } 27 | 28 | util.inherits(ResolvedAstCollector, SuperClass); 29 | 30 | (function(p) { 31 | p.setModules = setModules; 32 | function setModules(modules) { 33 | var m, _modules = {}; 34 | for (var id in modules) { 35 | m = modules[id]; 36 | if (!m.duplicateOf) { 37 | _modules[id] = m; 38 | } 39 | } 40 | // do not export module aliases 41 | this._modules = _modules; 42 | // use them only while resolving 43 | this._modulesWithDuplicates = modules; 44 | } 45 | 46 | p.getModuleIdAst = getModuleIdAst; 47 | function getModuleIdAst(m) { 48 | if (this.config.minify) { 49 | var moduleId = m.getHashCode(); 50 | if (this.config.minifyIdentifiers) { 51 | moduleId = generateId(moduleId); 52 | } 53 | return ["string", moduleId]; 54 | } else { 55 | while (m.duplicateOf) { 56 | m = m.duplicateOf; 57 | } 58 | return ["string", m.id]; 59 | } 60 | } 61 | 62 | p.renderRuntime = renderRuntime; 63 | function renderRuntime() { 64 | return RUNTIME; 65 | } 66 | 67 | p.getModuleAst = getModuleAst; 68 | function getModuleAst(m) { 69 | var modules = this._modulesWithDuplicates, 70 | self = this; 71 | function handleExpr(expr, args) { 72 | var firstArg = args[0]; 73 | if (expr[0] == "name" && expr[1] == "require" && firstArg[0] == 'string') { 74 | var ident = identifier.create(firstArg[1]); 75 | ident = m.resolveIdentifier(ident); 76 | args = args.slice(0); 77 | args[0] = self.getModuleIdAst(modules[ident]); 78 | return [this[0], expr, args]; 79 | } 80 | } 81 | 82 | return walker.with_walkers({ 83 | "new": handleExpr, 84 | "call": handleExpr 85 | }, function() { return walker.walk(m.ast); }); 86 | } 87 | })(ResolvedAstCollector.prototype); 88 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | path = require('path'), 3 | jsonFs = require('./lib/json-fs'), 4 | builder = require('./lib/builder'), 5 | logger = require('./lib/logger'), 6 | moduleGrapher = require('module-grapher'); 7 | 8 | exports.build = build; 9 | function build(main, config, callback) { 10 | if (!callback) { 11 | callback = config; 12 | config = {}; 13 | } 14 | if ((config.minify || config.minifyIdentifiers) && config.cache) { 15 | var err = new Error('Cannot minify code when using cache.'); 16 | callback(err); 17 | return; 18 | } 19 | if (config.resolveIdentifiers && config.cache) { 20 | var err = new Error('Cannot resolve identifiers when using cache.'); 21 | callback(err); 22 | return; 23 | } 24 | moduleGrapher.graph(main, config, function(err, result) { 25 | if (err) { 26 | callback(err); 27 | } else { 28 | result.output = builder.create(config).build(result); 29 | if (config.verbose) { logger.log(result); } 30 | callback(null, result); 31 | } 32 | }); 33 | } 34 | 35 | exports.buildFromPackage = function(p, configCallback, callback) { 36 | if (!callback) { 37 | callback = configCallback; 38 | configCallback = function() {}; 39 | } 40 | fs.stat(p, function(err, stat) { 41 | if (err) { 42 | callback(err); 43 | } else { 44 | var packageFile, root; 45 | if (stat.isDirectory()) { 46 | root = p; 47 | packageFile = path.join(p, 'package.json'); 48 | } else { 49 | root = path.dirname(p); 50 | packageFile = p; 51 | } 52 | jsonFs.readFile(packageFile, function(err, json) { 53 | if (err) { 54 | err.file = packageFile; 55 | err.longDesc = err.toString() + '. Malformed JSON in descriptor file:\n ' + packageFile; 56 | err.toString = function() { return err.longDesc; }; 57 | callback(err); 58 | } else { 59 | var config = json.modulr || {}; 60 | config.isPackageAware = true; 61 | config.root = root; 62 | configCallback(config); 63 | build(json.main, config, callback); 64 | } 65 | }); 66 | } 67 | }); 68 | }; 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "modulr", 3 | "description": "Resolves and concatenates CommonJS module dependencies for use in the browser.", 4 | "main": "./main", 5 | "version": "0.14.0", 6 | "dependencies": { 7 | "module-grapher": ">=0.11.0", 8 | "uglify-js": "1.x" 9 | }, 10 | "author": "Tobie Langel (http://tobielangel.com)", 11 | "maintainers": [ 12 | "Tobie Langel (http://tobielangel.com)" 13 | ], 14 | "contributors": [ 15 | "Tobie Langel (http://tobielangel.com)", 16 | "Chris Tice ", 17 | "Andrew Chien ", 18 | "Vladimir Kolesnikov " 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "git://github.com/tobie/modulr-node.git" 23 | }, 24 | "licenses": [ 25 | { 26 | "type": "MIT", 27 | "url": "http://github.com/tobie/modulr-node/raw/master/LICENSE" 28 | }, 29 | { 30 | "type": "Apache-2.0", 31 | "url": "http://github.com/tobie/modulr-node/raw/master/LICENSE" 32 | } 33 | ], 34 | "devDependencies": {}, 35 | "engines": { 36 | "node": "*" 37 | }, 38 | "optionalDependencies": {} 39 | } 40 | --------------------------------------------------------------------------------