├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── gulpfile.js ├── lib ├── export.js ├── index.js ├── parse.js ├── trace.js └── util.js ├── package.json ├── src ├── export.coffee ├── index.coffee ├── parse.coffee ├── trace.coffee └── util.coffee └── test ├── expected ├── comment.js.map.json ├── foo.js.map.json ├── index.js.map.json ├── test.json.js └── text.html.js ├── fixtures ├── comments │ └── comment.js ├── commonjs │ ├── bar.js │ ├── faz.js │ ├── foo.js │ └── fuz.js ├── config │ ├── config.js │ └── index.js ├── core │ ├── bar.js │ ├── duu.js │ ├── duz.js │ ├── foo.coffee │ ├── foo.js │ ├── fuz │ │ └── ahah.js │ ├── index.coffee │ ├── index.js │ ├── inline.js │ ├── nested.js │ ├── nested_requirejs.js │ ├── plugin-json.js │ ├── plugin-text.js │ ├── plugin.js │ ├── relative.js │ ├── require_exports.js │ ├── test.json │ └── text.html ├── deps │ ├── config.js │ ├── deps │ │ └── extra │ │ │ ├── extra.js │ │ │ └── lib │ │ │ └── helper.js │ └── src │ │ └── index.js ├── errors │ ├── cycle1.js │ ├── cycle2.js │ ├── cycle_exports1.js │ ├── cycle_exports2.js │ ├── foo.coffee │ ├── foo.js │ ├── multiple_anonymous_defines.js │ └── return.js ├── inexclude │ ├── bar.js │ ├── baz.js │ └── foo.js ├── shim │ ├── amd.js │ ├── index.js │ ├── no_amd.js │ ├── no_amd2.js │ └── no_shim.js └── shortcuts │ ├── config.js │ ├── index.js │ └── path │ └── to │ └── module │ └── foo.js └── index_test.coffee /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .tmp 3 | node_modules 4 | build 5 | dist 6 | public -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | 4 | node_js: 5 | - "node" 6 | 7 | before_install: 8 | - npm install -g gulp 9 | 10 | install: 11 | - npm install 12 | 13 | os: 14 | #- osx 15 | - linux 16 | 17 | script: npm test 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 scalable minds 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project is no longer actively maintained. Mostly, because we use [Webpack](http://webpack.github.io/) in our projects now. I am happy to review incoming PRs, though. If you would like to become maintainer, please contact me. – [@normanrz](https://github.com/normanrz) 2 | 3 | # amd-optimize [![Build Status](https://travis-ci.org/scalableminds/amd-optimize.svg?branch=master)](https://travis-ci.org/scalableminds/amd-optimize) 4 | 5 | > An AMD ([RequireJS](http://requirejs.org/)) optimizer that's stream-friendly. Made for [gulp](http://gulpjs.com/). (WIP) 6 | 7 | 8 | # Features 9 | 10 | * Trace all dependencies of an AMD module 11 | * Stream-friendly: Pipe in files and get an ordered stream of files out. No need for writing on disk in between. 12 | * Support for precompilation of source files (ie. CoffeeScript) 13 | * Wraps non-AMD dependencies 14 | * Supply a custom loader for on-demand loading 15 | * Leaves concatenation and minification to your preferred choice of modules 16 | * [gulp-sourcemaps](https://www.npmjs.org/package/gulp-sourcemaps) support 17 | 18 | # Example 19 | 20 | ```js 21 | var gulp = require("gulp"); 22 | var amdOptimize = require("amd-optimize"); 23 | var concat = require('gulp-concat'); 24 | 25 | gulp.task("scripts:index", function () { 26 | 27 | return gulp.src("src/scripts/**/*.js") 28 | // Traces all modules and outputs them in the correct order. 29 | .pipe(amdOptimize("main")) 30 | .pipe(concat("index.js")) 31 | .pipe(gulp.dest("dist/scripts")); 32 | 33 | }); 34 | ``` 35 | 36 | # Motivation 37 | This aims to be an alternative to the powerful [r.js](https://github.com/jrburke/r.js) optimizer, but made for a streaming environment like [gulp](http://gulpjs.com/). This implementation doesn't operate on the file system directly. So, there's no need for complicated setups when dealing with precompiled files. Also, this module only focuses on tracing modules and does not intend replace a full-fletched build system. Therefore, there might be tons of use cases where r.js is a better fit. 38 | 39 | 40 | # Installation 41 | 42 | ```bash 43 | $ npm install amd-optimize 44 | ``` 45 | 46 | 47 | ## API 48 | 49 | ### amdOptimize(moduleName, [options]) 50 | 51 | #### moduleName 52 | Type: `String` 53 | 54 | #### options.paths 55 | 56 | ```js 57 | paths : { 58 | "backbone" : "../bower_components/backbone/backbone", 59 | "jquery" : "../bower_components/jquery/jquery" 60 | } 61 | ``` 62 | 63 | #### options.map 64 | 65 | ```js 66 | map : { 67 | // Replace underscore with lodash for the backbone module 68 | "backbone" : { 69 | "underscore" : "lodash" 70 | } 71 | } 72 | ``` 73 | 74 | #### options.shim 75 | 76 | ```js 77 | shim : { 78 | // Shimmed export. Specify the variable name that is being exported. 79 | "three" : { 80 | exports : "THREE" 81 | }, 82 | 83 | // Shimmed dependecies and export 84 | "three.color" : { 85 | deps : ["three"], 86 | exports : "THREE.ColorConverter" 87 | }, 88 | 89 | // Shimmed dependencies 90 | "bootstrap" : ["jquery"] 91 | } 92 | ``` 93 | 94 | #### options.configFile 95 | Type: `Stream` or `String` 96 | 97 | Supply a filepath (can be a glob) or a gulp stream to your config file that lists all your paths, shims and maps. 98 | 99 | ```js 100 | amdOptimize.src("index", { 101 | configFile : "src/scripts/require_config.js" 102 | }); 103 | 104 | amdOptimize.src("index", { 105 | configFile : gulp.src("src/scripts/require_config.coffee").pipe(coffee()) 106 | }); 107 | ``` 108 | 109 | #### options.findNestedDependencies 110 | Type: `Boolean` 111 | Default: `false` 112 | 113 | 114 | If `true` it will trace `require()` dependencies inside of top-level `require()` or `define()` calls. Usually, these nested dependencies are considered dynamic or runtime calls, so it's disabled by default. 115 | 116 | Would trace both `router` and `controllers/home`: 117 | 118 | ```js 119 | define("router", [], function () { 120 | return { 121 | "/home" : function () { 122 | require(["controllers/home"]); 123 | }, 124 | ... 125 | } 126 | }) 127 | ``` 128 | 129 | #### options.baseUrl 130 | #### options.exclude 131 | #### options.include 132 | 133 | #### options.wrapShim 134 | 135 | Type: `Boolean` 136 | Default: `false` 137 | 138 | If `true` all files that you have declared a shim for and don't have a proper `define()` call will be wrapped in a `define()` call. 139 | 140 | 141 | 142 | ```js 143 | // Original 144 | var test = "Test"; 145 | 146 | // Output 147 | define("test", [], function () { 148 | var test = "Test"; 149 | return test; 150 | }); 151 | 152 | // Shim config 153 | shim : { 154 | test : { 155 | exports : "test" 156 | } 157 | } 158 | ``` 159 | 160 | #### options.preserveFiles 161 | 162 | Type: `Boolean` 163 | Default: `false` 164 | 165 | If `true` all files that you combine will not be altered from the source, should be used for outputted files to match the original source file, good for debugging and inline sourcemaps. A good code minifier or uglify will remove comments and strip new lines anyway. 166 | 167 | 168 | #### options.loader 169 | WIP. Subject to change. 170 | 171 | ```js 172 | amdOptimize.src( 173 | "index", 174 | loader : amdOptimize.loader( 175 | // Used for turning a moduleName into a filepath glob. 176 | function (moduleName) { return "src/scripts/" + moduleName + ".coffee" }, 177 | // Returns a transform stream. 178 | function () { return coffee(); } 179 | ) 180 | ) 181 | ``` 182 | 183 | ### amdOptimize.src(moduleName, options) 184 | Same as `amdOptimize()`, but won't accept an input stream. Instead it will rely on loading the files by itself. 185 | 186 | 187 | ## Algorithms 188 | ### Resolving paths 189 | 190 | ### Finding files 191 | 1. Check the input stream. 192 | 2. Look for files with the default loader and `baseUrl`. 193 | 3. Look for files with the custom loader and its transform streams. 194 | 4. Give up. 195 | 196 | 197 | ## Recommended modules 198 | * [gulp-concat](https://www.npmjs.org/package/gulp-concat/): Concat the output files. Because that's the whole point of module optimization, right? 199 | 200 | ```js 201 | var concat = require("gulp-concat"); 202 | 203 | gulp.src("src/scripts/**/*.js") 204 | .pipe(amdOptimize("index")) 205 | .pipe(concat("index.js")) 206 | .pipe(gulp.dest("dist")); 207 | ``` 208 | 209 | 210 | * [gulp-uglify](https://www.npmjs.org/package/gulp-uglify/): Minify the output files. 211 | 212 | ```js 213 | var uglify = require("gulp-uglify"); 214 | 215 | gulp.src("src/scripts/**/*.js") 216 | .pipe(amdOptimize("index")) 217 | .pipe(concat("index.js")) 218 | .pipe(uglify()) 219 | .pipe(gulp.dest("dist")); 220 | ``` 221 | 222 | 223 | * [gulp-coffee](https://www.npmjs.org/package/gulp-coffee/): Precompile CoffeeScript source files. Or any other [language that compiles to JS](https://github.com/jashkenas/coffee-script/wiki/List-of-languages-that-compile-to-JS). 224 | 225 | ```js 226 | var coffee = require("gulp-coffee"); 227 | 228 | gulp.src("src/scripts/**/*.coffee") 229 | .pipe(coffee()) 230 | .pipe(amdOptimize("index")) 231 | .pipe(concat("index.js")) 232 | .pipe(gulp.dest("dist")); 233 | ``` 234 | 235 | 236 | * [gulp-if](https://www.npmjs.org/package/gulp-if/): Conditionally pipe files through a transform stream. Useful for CoffeeScript precompilation. 237 | 238 | ```js 239 | var gif = require("gulp-if"); 240 | 241 | gulp.src("src/scripts/**/*.{coffee,js}") 242 | .pipe(gif(function (file) { return path.extname(file) == ".coffee"; }, coffee())) 243 | .pipe(amdOptimize("index")) 244 | .pipe(concat("index.js")) 245 | .pipe(gulp.dest("dist")); 246 | ``` 247 | 248 | * [event-stream](https://www.npmjs.org/package/event-stream/), [gulp-order](https://www.npmjs.org/package/gulp-order): Add files before or after 249 | 250 | ```js 251 | var eventStream = require("event-stream"); 252 | var order = require("gulp-order"); 253 | 254 | eventStream.merge( 255 | gulp.src("bower_components/almond/almond.js"), 256 | gulp.src(amdOptimize("index")) 257 | .pipe(concat("index.js")) 258 | ) 259 | .pipe(order(["**/almond.js", "**/index.js"])) 260 | .pipe(concat("index.js")) 261 | .pipe(gulp.dest("dist")); 262 | 263 | ``` 264 | 265 | ## Current limitations 266 | * No [RequireJS plugins](http://requirejs.org/docs/api.html#plugins). Except for the `text` plugin. 267 | * No [`exclude` or `include` configuration](http://requirejs.org/docs/optimization.html#basics). 268 | * No [circular dependencies](http://requirejs.org/docs/api.html#circular) 269 | 270 | ## Tests 271 | 1. Install npm dev dependencies `npm install` 272 | 2. Install gulp globally `npm install -g gulp` 273 | 3. Run `gulp test` 274 | 275 | ## License 276 | MIT © scalable minds 2014 277 | 278 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | require("coffee-script").register(); 2 | 3 | var gulp = require("gulp"); 4 | var coffee = require("gulp-coffee"); 5 | var gif = require("gulp-if"); 6 | var mocha = require("gulp-mocha"); 7 | var concat = require("gulp-concat"); 8 | var util = require("gulp-util"); 9 | var uglify = require("gulp-uglify"); 10 | var rjs = require("./src/index.coffee"); 11 | 12 | var path = require("path"); 13 | var through = require("through2"); 14 | 15 | 16 | gulp.task("compile", function (){ 17 | return gulp.src("src/**/*.coffee") 18 | .pipe(coffee()) 19 | .pipe(gulp.dest("lib")); 20 | }); 21 | 22 | 23 | gulp.task("test", ["compile"], function () { 24 | return gulp.src("test/*_test.coffee") 25 | .pipe(mocha({ reporter : "spec" })); 26 | }); 27 | 28 | 29 | function logger() { 30 | return through.obj(function (file, enc, callback) { 31 | util.log(">>", util.colors.yellow(path.relative(process.cwd(), file.path))); 32 | callback(null, file); 33 | }); 34 | } 35 | 36 | 37 | gulp.task("sample", function () { 38 | return gulp.src("test/fixtures/core/**/*.js") 39 | .pipe(rjs("nested_requirejs")) 40 | .pipe(gulp.dest(".tmp")); 41 | }) 42 | 43 | gulp.task("example", function () { 44 | return gulp.src("build/{javascripts,bower_components}/**/*.{js,coffee}") 45 | .pipe(gif(function (file) { return path.extname(file.path) == ".coffee"; }, coffee())) 46 | // Traces all modules and outputs them in the correct order. Also wraps shimmed modules. 47 | .pipe(rjs("index", { 48 | configFile : gulp.src("build/javascripts/require_config.coffee").pipe(coffee()), 49 | wrapShim : true, 50 | baseUrl : "build/javascripts", 51 | paths : { 52 | cordova : "empty:", 53 | underscore : "../bower_components/lodash/dist/lodash" 54 | } 55 | })) 56 | .pipe(concat("index.js")) 57 | .pipe(logger()) 58 | .pipe(gulp.dest("dist")); 59 | }); 60 | 61 | gulp.task("example2", function () { 62 | // Traces all modules and outputs them in the correct order. Also wraps shimmed modules. 63 | var source = rjs("main", { 64 | configFile : gulp.src("public/javascripts/require_config.coffee").pipe(coffee()), 65 | wrapShim : true, 66 | baseUrl : "public/javascripts", 67 | paths : { 68 | routes : "empty:" 69 | }, 70 | loader : rjs.loader( 71 | function (moduleName) { return path.join("public/javascripts", moduleName + ".{js,coffee}") }, 72 | function () { return gif(function (file) { return path.extname(file.path) == ".coffee"; }, coffee()) } 73 | ) 74 | }); 75 | 76 | source.end(); 77 | return source 78 | .pipe(concat("main.js")) 79 | .pipe(uglify()) 80 | .pipe(gulp.dest("dist")) 81 | .pipe(logger()); 82 | }); 83 | 84 | gulp.task("default", ["compile", "test"]); 85 | 86 | 87 | -------------------------------------------------------------------------------- /lib/export.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var _, b, escodegen, fixModule, path, through, vinylSourcemapsApply; 3 | 4 | _ = require("lodash"); 5 | 6 | b = require("ast-types").builders; 7 | 8 | escodegen = require("escodegen"); 9 | 10 | through = require("through2"); 11 | 12 | path = require("path"); 13 | 14 | vinylSourcemapsApply = require("vinyl-sourcemaps-apply"); 15 | 16 | module.exports = fixModule = function(options) { 17 | if (options == null) { 18 | options = {}; 19 | } 20 | options = _.defaults(options, { 21 | wrapShim: true 22 | }); 23 | return through.obj(function(module, enc, done) { 24 | var ast, defineBody, defineCall, defineReturnStatement, generatedCode, sourceFile; 25 | if (module.isShallow) { 26 | done(); 27 | return; 28 | } 29 | ast = module.file.ast; 30 | delete module.file.ast; 31 | if (!module.hasDefine) { 32 | defineReturnStatement = b.returnStatement(module.exports ? b.identifier(module.exports) : null); 33 | if (options.wrapShim && module.isShimmed) { 34 | defineBody = ast.body.concat([defineReturnStatement]); 35 | } else { 36 | defineBody = [defineReturnStatement]; 37 | } 38 | defineCall = b.callExpression(b.identifier("define"), [ 39 | b.literal(module.name), b.arrayExpression(module.deps.map(function(dep) { 40 | return b.literal(dep.name); 41 | })), b.functionExpression(null, [], b.blockStatement(defineBody)) 42 | ]); 43 | if (options.wrapShim && module.isShimmed) { 44 | ast.body = [b.expressionStatement(defineCall)]; 45 | } else { 46 | ast.body.push(b.expressionStatement(defineCall)); 47 | } 48 | } else if (module.isAnonymous) { 49 | module.astNodes.forEach((function(_this) { 50 | return function(astNode) { 51 | if (astNode.callee.name === "define" && (astNode["arguments"].length === 1 || (astNode["arguments"].length === 2 && astNode["arguments"][0].type === "ArrayExpression"))) { 52 | return astNode["arguments"] = [ 53 | b.literal(module.name), b.arrayExpression(module.deps.map(function(dep) { 54 | return b.literal(dep.name); 55 | })), _.last(astNode["arguments"]) 56 | ]; 57 | } 58 | }; 59 | })(this)); 60 | } 61 | if (module.hasDefine && module.isShimmed) { 62 | ast.body = [b.expressionStatement(b.callExpression(b.memberExpression(b.functionExpression(null, [], b.blockStatement(ast.body)), b.identifier("call"), false), [b.thisExpression()]))]; 63 | } 64 | sourceFile = module.file.clone(); 65 | sourceFile.sourceMap = module.file.sourceMap; 66 | if (sourceFile.sourceMap) { 67 | generatedCode = escodegen.generate(ast, { 68 | comment: options.preserveComments, 69 | sourceMap: true, 70 | sourceMapWithCode: true, 71 | file: sourceFile.sourceMap.file 72 | }); 73 | sourceFile.contents = new Buffer(generatedCode.code, "utf8"); 74 | vinylSourcemapsApply(sourceFile, generatedCode.map.toJSON()); 75 | } else if (!options.preserveFiles) { 76 | sourceFile = module.file.clone(); 77 | sourceFile.contents = new Buffer(escodegen.generate(ast, { 78 | comment: options.preserveComments 79 | }), "utf8"); 80 | } 81 | this.push(sourceFile); 82 | return done(); 83 | }); 84 | }; 85 | 86 | }).call(this); 87 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var Readable, _, async, collectModules, defaultLoader, exportModule, firstChunk, fs, mergeOptionsFile, path, rjs, through, trace, util, vinylFs; 3 | 4 | _ = require("lodash"); 5 | 6 | fs = require("fs"); 7 | 8 | path = require("path"); 9 | 10 | vinylFs = require("vinyl-fs"); 11 | 12 | async = require("async"); 13 | 14 | through = require("through2"); 15 | 16 | Readable = require("stream").Readable; 17 | 18 | trace = require("./trace"); 19 | 20 | exportModule = require("./export"); 21 | 22 | util = require("./util"); 23 | 24 | firstChunk = function(stream, callback) { 25 | var settled; 26 | settled = false; 27 | stream.on("data", function(data) { 28 | if (!settled) { 29 | settled = true; 30 | callback(null, data); 31 | } 32 | }).on("end", function() { 33 | if (!settled) { 34 | callback(); 35 | } 36 | }).on("error", function(err) { 37 | if (!settled) { 38 | settled = true; 39 | callback(err); 40 | } 41 | }); 42 | }; 43 | 44 | collectModules = function(module, omitInline) { 45 | var collector, outputBuffer; 46 | if (omitInline == null) { 47 | omitInline = true; 48 | } 49 | outputBuffer = []; 50 | collector = function(currentModule) { 51 | currentModule.deps.forEach(function(depModule) { 52 | return collector(depModule); 53 | }); 54 | if (!(omitInline && currentModule.isInline) && !_.any(outputBuffer, { 55 | name: currentModule.name 56 | })) { 57 | return outputBuffer.push(currentModule); 58 | } 59 | }; 60 | collector(module); 61 | return outputBuffer; 62 | }; 63 | 64 | mergeOptionsFile = function(file, options) { 65 | if (options == null) { 66 | options = {}; 67 | } 68 | return _.merge({}, Function("var output,\n requirejs = require = function() {},\n define = function () {};\nrequire.config = function (options) { output = options; };\n" + (file.contents.toString("utf8")) + ";\nreturn output;")(), options); 69 | }; 70 | 71 | defaultLoader = function(fileBuffer, options) { 72 | return function(name, callback, asPlainFile) { 73 | var addJs, file; 74 | addJs = (!asPlainFile) && '.js' || ''; 75 | if (options.baseUrl && (file = _.detect(fileBuffer, { 76 | path: path.resolve(options.baseUrl, name + addJs) 77 | }))) { 78 | return callback(null, file); 79 | } else if (file = _.detect(fileBuffer, { 80 | relative: path.join(options.baseUrl, name + addJs) 81 | })) { 82 | return callback(null, file); 83 | } else if (options.loader) { 84 | return options.loader(name, callback); 85 | } else { 86 | return module.exports.loader()(path.join(options.baseUrl, name + addJs), callback); 87 | } 88 | }; 89 | }; 90 | 91 | module.exports = rjs = function(entryModuleName, options) { 92 | var configFileStream, fileBuffer, mainStream; 93 | if (options == null) { 94 | options = {}; 95 | } 96 | options = _.defaults(options, { 97 | baseUrl: "", 98 | configFile: null, 99 | exclude: [], 100 | excludeShallow: [], 101 | findNestedDependencies: false, 102 | loader: null, 103 | preserveComments: false, 104 | preserveFiles: false 105 | }); 106 | if (_.isString(options.exclude)) { 107 | options.exclude = [options.exclude]; 108 | } 109 | if (_.isString(options.excludeShallow)) { 110 | options.excludeShallow = [options.excludeShallow]; 111 | } 112 | if (_.isString(options.configFile) || _.isArray(options.configFile)) { 113 | configFileStream = vinylFs.src(options.configFile); 114 | } else if (_.isObject(options.configFile)) { 115 | configFileStream = options.configFile; 116 | } 117 | fileBuffer = []; 118 | mainStream = through.obj(function(file, enc, done) { 119 | fileBuffer.push(file); 120 | return done(); 121 | }, function(done) { 122 | return async.waterfall([ 123 | function(callback) { 124 | if (configFileStream) { 125 | return configFileStream.pipe(through.obj(function(file, enc, done) { 126 | options = mergeOptionsFile(file, options); 127 | return done(); 128 | }, function() { 129 | return callback(); 130 | })); 131 | } else { 132 | return callback(); 133 | } 134 | }, function(callback) { 135 | return trace(entryModuleName, options, null, defaultLoader(fileBuffer, options), callback); 136 | }, function(module, callback) { 137 | return callback(null, collectModules(module)); 138 | }, function(modules, callback) { 139 | if (_.isArray(options.exclude)) { 140 | return async.map(options.exclude, function(moduleName, callback) { 141 | return trace(moduleName, options, null, defaultLoader(fileBuffer, options), callback); 142 | }, function(err, excludedModules) { 143 | if (err) { 144 | return callback(err); 145 | } else { 146 | return callback(null, modules, _(excludedModules).map(function(module) { 147 | return collectModules(module); 148 | }).flatten().pluck("name").unique().value()); 149 | } 150 | }); 151 | } else { 152 | return callback(null, modules, []); 153 | } 154 | }, function(modules, excludedModuleNames, callback) { 155 | var exportStream; 156 | modules = _.reject(modules, function(module) { 157 | return _.contains(excludedModuleNames, module.name) || _.contains(options.excludeShallow, module.name); 158 | }); 159 | exportStream = exportModule(options); 160 | exportStream.on("data", function(file) { 161 | return mainStream.push(file); 162 | }).on("end", function() { 163 | return callback(); 164 | }).on("error", callback); 165 | modules.forEach(exportStream.write.bind(exportStream)); 166 | return exportStream.end(); 167 | } 168 | ], done); 169 | }); 170 | return mainStream; 171 | }; 172 | 173 | module.exports.src = function(moduleName, options) { 174 | var source; 175 | source = rjs(moduleName, options); 176 | process.nextTick(function() { 177 | return source.end(); 178 | }); 179 | return source; 180 | }; 181 | 182 | module.exports.loader = function(filenameResolver, pipe) { 183 | return function(moduleName, callback) { 184 | var filename, source; 185 | if (filenameResolver) { 186 | filename = filenameResolver(moduleName); 187 | } else { 188 | filename = moduleName; 189 | } 190 | source = vinylFs.src(filename).pipe(through.obj()); 191 | if (pipe) { 192 | source = source.pipe(pipe()); 193 | } 194 | firstChunk(source, callback); 195 | }; 196 | }; 197 | 198 | }).call(this); 199 | -------------------------------------------------------------------------------- /lib/parse.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var _, acorn, escodegen, parseRequireDefinitions, valuesFromArrayExpression, walk; 3 | 4 | _ = require("lodash"); 5 | 6 | acorn = require("acorn"); 7 | 8 | escodegen = require("escodegen"); 9 | 10 | walk = require("acorn/util/walk"); 11 | 12 | valuesFromArrayExpression = function(expr) { 13 | return expr.elements.map(function(a) { 14 | return a.value; 15 | }); 16 | }; 17 | 18 | module.exports = parseRequireDefinitions = function(config, file, callback) { 19 | var ast, comments, definitions, err, tokens; 20 | try { 21 | if (config.preserveComments) { 22 | comments = []; 23 | tokens = []; 24 | ast = acorn.parse(file.stringContents, { 25 | sourceFile: file.relative, 26 | locations: file.sourceMap != null, 27 | ranges: true, 28 | onComment: comments, 29 | onToken: tokens 30 | }); 31 | escodegen.attachComments(ast, comments, tokens); 32 | } else { 33 | ast = acorn.parse(file.stringContents, { 34 | sourceFile: file.relative, 35 | locations: file.sourceMap != null 36 | }); 37 | } 38 | } catch (_error) { 39 | err = _error; 40 | if (err instanceof SyntaxError) { 41 | err.filename = file.path; 42 | err.message += " in " + file.path; 43 | } 44 | callback(err); 45 | return; 46 | } 47 | file.ast = ast; 48 | definitions = []; 49 | walk.ancestor(ast, { 50 | CallExpression: function(node, state) { 51 | var defineAncestors, deps, isInsideDefine, moduleName; 52 | if (node.callee.name === "define") { 53 | switch (node["arguments"].length) { 54 | case 1: 55 | if (node["arguments"][0].type === "FunctionExpression" && node["arguments"][0].params.length > 0) { 56 | deps = ['require', 'exports', 'module']; 57 | walk.simple(node["arguments"][0], { 58 | CallExpression: function(node) { 59 | if (node.callee.name === "require" || node.callee.name === "requirejs") { 60 | return deps.push(node["arguments"][0].value); 61 | } 62 | } 63 | }); 64 | } 65 | break; 66 | case 2: 67 | switch (node["arguments"][0].type) { 68 | case "Literal": 69 | moduleName = node["arguments"][0].value; 70 | break; 71 | case "ArrayExpression": 72 | deps = valuesFromArrayExpression(node["arguments"][0]); 73 | } 74 | break; 75 | case 3: 76 | moduleName = node["arguments"][0].value; 77 | deps = valuesFromArrayExpression(node["arguments"][1]); 78 | } 79 | definitions.push({ 80 | method: "define", 81 | moduleName: moduleName, 82 | deps: deps != null ? deps : [], 83 | argumentsLength: node["arguments"].length, 84 | node: node 85 | }); 86 | isInsideDefine = true; 87 | } 88 | if ((node.callee.name === "require" || node.callee.name === "requirejs") && node["arguments"].length > 0 && node["arguments"][0].type === "ArrayExpression") { 89 | defineAncestors = _.any(state.slice(0, -1), function(ancestorNode) { 90 | return ancestorNode.type === "CallExpression" && (ancestorNode.callee.name === "define" || ancestorNode.callee.name === "require" || ancestorNode.callee.name === "requirejs"); 91 | }); 92 | if (config.findNestedDependencies || !defineAncestors) { 93 | return definitions.push({ 94 | method: "require", 95 | moduleName: void 0, 96 | deps: valuesFromArrayExpression(node["arguments"][0]), 97 | node: node 98 | }); 99 | } 100 | } 101 | } 102 | }); 103 | callback(null, file, definitions); 104 | }; 105 | 106 | }).call(this); 107 | -------------------------------------------------------------------------------- /lib/trace.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var Module, VinylFile, _, async, fs, parse, path, traceModule, util; 3 | 4 | _ = require("lodash"); 5 | 6 | fs = require("fs"); 7 | 8 | path = require("path"); 9 | 10 | async = require("async"); 11 | 12 | VinylFile = require("vinyl"); 13 | 14 | parse = require("./parse"); 15 | 16 | util = require("./util"); 17 | 18 | Module = (function() { 19 | function Module(name, file1, deps1) { 20 | this.name = name; 21 | this.file = file1; 22 | this.deps = deps1 != null ? deps1 : []; 23 | this.name = util.fixModuleName(this.name); 24 | this.isShallow = false; 25 | this.isShimmed = false; 26 | this.isAnonymous = false; 27 | this.isInline = false; 28 | this.hasDefine = false; 29 | this.astNodes = []; 30 | } 31 | 32 | return Module; 33 | 34 | })(); 35 | 36 | module.exports = traceModule = function(startModuleName, config, allModules, fileLoader, callback) { 37 | var emitModule, foundModuleNames, jsonFiles, resolveInlinedModule, resolveModule, resolveModuleFileName, resolveModuleName, resolveModules, textFiles; 38 | if (allModules == null) { 39 | allModules = []; 40 | } 41 | foundModuleNames = []; 42 | textFiles = {}; 43 | jsonFiles = {}; 44 | resolveModuleName = function(moduleName, relativeTo) { 45 | var eligiblePath, isJson, isText, relativeToFileName, slashIdx; 46 | if (relativeTo == null) { 47 | relativeTo = ""; 48 | } 49 | isText = moduleName.indexOf('text!') !== -1; 50 | if (isText) { 51 | moduleName = moduleName.replace('text!', ''); 52 | } 53 | isJson = moduleName.indexOf('json!') !== -1; 54 | if (isJson) { 55 | moduleName = moduleName.replace('json!', ''); 56 | } 57 | if (config.paths && !config.paths[moduleName]) { 58 | slashIdx = moduleName.indexOf("/"); 59 | if (slashIdx > 0) { 60 | eligiblePath = config.paths[moduleName.substr(0, slashIdx)]; 61 | if (eligiblePath) { 62 | moduleName = eligiblePath + moduleName.substr(slashIdx); 63 | } 64 | } 65 | } 66 | relativeToFileName = resolveModuleFileName(relativeTo); 67 | if (moduleName[0] === ".") { 68 | moduleName = util.fixModuleName(path.join(path.dirname(relativeToFileName), moduleName)); 69 | } 70 | if (config.map && config.map[relativeTo] && config.map[relativeTo][moduleName]) { 71 | moduleName = config.map[relativeTo][moduleName]; 72 | } 73 | if (isText) { 74 | textFiles[moduleName] = true; 75 | } 76 | if (isJson) { 77 | jsonFiles[moduleName] = true; 78 | } 79 | return moduleName; 80 | }; 81 | resolveModuleFileName = function(moduleName) { 82 | if (config.paths && config.paths[moduleName]) { 83 | moduleName = config.paths[moduleName]; 84 | } 85 | if (/!|^exports$|^require$|^module$|^empty:/.test(moduleName)) { 86 | 87 | } else { 88 | return moduleName; 89 | } 90 | }; 91 | resolveModules = function(moduleNames, callback) { 92 | async.mapSeries(moduleNames, resolveModule, callback); 93 | }; 94 | resolveInlinedModule = function(moduleName, deps, astNode, vinylFile, callback) { 95 | async.waterfall([ 96 | function(callback) { 97 | return resolveModules(deps, callback); 98 | }, function(modules, callback) { 99 | var module; 100 | module = new Module(moduleName, vinylFile, _.compact(modules)); 101 | module.hasDefine = true; 102 | module.isInline = true; 103 | module.astNodes.push(astNode); 104 | emitModule(module); 105 | return callback(); 106 | } 107 | ], callback); 108 | }; 109 | resolveModule = function(moduleName, callback) { 110 | var fileName, isJsonFile, isTextFile, module; 111 | module = _.detect(allModules, { 112 | name: moduleName 113 | }); 114 | if (module) { 115 | callback(null, module); 116 | return; 117 | } 118 | fileName = resolveModuleFileName(moduleName); 119 | if (!fileName) { 120 | module = new Module(moduleName); 121 | module.isShallow = true; 122 | callback(null, emitModule(module)); 123 | return; 124 | } 125 | if (_.contains(foundModuleNames, moduleName)) { 126 | callback(new Error("Circular dependency detected. Module '" + moduleName + "' has been processed before.")); 127 | return; 128 | } else { 129 | foundModuleNames.push(moduleName); 130 | } 131 | module = null; 132 | isTextFile = !!textFiles[moduleName]; 133 | isJsonFile = !!jsonFiles[moduleName]; 134 | async.waterfall([ 135 | function(callback) { 136 | return fileLoader(fileName, callback, isTextFile || isJsonFile); 137 | }, function(file, callback) { 138 | if (arguments.length === 1) { 139 | callback = file; 140 | file = null; 141 | } 142 | if (file) { 143 | return callback(null, file); 144 | } else { 145 | return callback(new Error("No file for module '" + moduleName + "' found.")); 146 | } 147 | }, function(file, callback) { 148 | file.stringContents = file.contents.toString("utf8"); 149 | if (isTextFile) { 150 | file.stringContents = 'define(function(){ return ' + JSON.stringify(file.stringContents) + '; });'; 151 | } 152 | if (isJsonFile) { 153 | file.stringContents = 'define(function(){ return JSON.parse(' + JSON.stringify(file.stringContents) + '); });'; 154 | } 155 | module = new Module(moduleName, file); 156 | return callback(null, file); 157 | }, parse.bind(null, config), function(file, definitions, callback) { 158 | if (_.filter(definitions, function(def) { 159 | var ref; 160 | return def.method === "define" && def.moduleName === void 0 && (0 < (ref = def.argumentsLength) && ref < 3); 161 | }).length > 1) { 162 | callback(new Error("A module must not have more than one anonymous 'define' calls.")); 163 | return; 164 | } 165 | module.hasDefine = _.any(definitions, function(def) { 166 | return def.method === "define" && (def.moduleName === void 0 || def.moduleName === moduleName); 167 | }); 168 | return async.mapSeries(definitions, function(def, callback) { 169 | def.deps = def.deps.map(function(depName) { 170 | var ref; 171 | return resolveModuleName(depName, (ref = def.moduleName) != null ? ref : moduleName); 172 | }); 173 | if (def.method === "define" && def.moduleName !== void 0 && def.moduleName !== moduleName) { 174 | async.waterfall([ 175 | function(callback) { 176 | return resolveInlinedModule(def.moduleName, def.deps, def.node, file, callback); 177 | }, function(callback) { 178 | return callback(null, []); 179 | } 180 | ], callback); 181 | } else { 182 | module.astNodes.push(def.node); 183 | resolveModules(def.deps, callback); 184 | } 185 | }, callback); 186 | }, function(unflatModules, callback) { 187 | return callback(null, _.compact(_.flatten(unflatModules))); 188 | }, function(depModules, callback) { 189 | var ref; 190 | (ref = module.deps).push.apply(ref, depModules); 191 | module.isAnonymous = true; 192 | async.waterfall([ 193 | function(callback) { 194 | var additionalDepNames, shim; 195 | additionalDepNames = null; 196 | if (config.shim && (shim = config.shim[module.name])) { 197 | if (module.hasDefine) { 198 | console.log("[warn]", "Module '" + module.name + "' is shimmed even though it has a proper define."); 199 | } 200 | module.isShimmed = true; 201 | if (shim.exports) { 202 | module.exports = shim.exports; 203 | } 204 | if (_.isArray(shim)) { 205 | additionalDepNames = shim; 206 | } else if (shim.deps) { 207 | additionalDepNames = shim.deps; 208 | } 209 | } 210 | if (additionalDepNames) { 211 | return resolveModules(additionalDepNames, callback); 212 | } else { 213 | return callback(null, []); 214 | } 215 | }, function(depModules, callback) { 216 | var ref1; 217 | (ref1 = module.deps).push.apply(ref1, depModules); 218 | return callback(null, emitModule(module)); 219 | } 220 | ], callback); 221 | } 222 | ], callback); 223 | }; 224 | emitModule = function(module) { 225 | if (!_.any(allModules, { 226 | name: module.name 227 | })) { 228 | allModules.push(module); 229 | } 230 | return module; 231 | }; 232 | resolveModule(startModuleName, callback); 233 | }; 234 | 235 | }).call(this); 236 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var fixModuleName, logger, path, printTree, through; 3 | 4 | path = require("path"); 5 | 6 | through = require("through2"); 7 | 8 | module.exports.printTree = printTree = function(currentModule, prefix) { 9 | var depPrefix; 10 | if (prefix == null) { 11 | prefix = ""; 12 | } 13 | console.log(prefix, currentModule.name, "(" + (path.relative(process.cwd(), currentModule.file.relative)) + ")"); 14 | depPrefix = prefix.replace("├", "|").replace("└", " ").replace(/─/g, " "); 15 | return currentModule.deps.forEach(function(depModule, i) { 16 | if (i + 1 < currentModule.deps.length) { 17 | return printTree(depModule, depPrefix + " ├──"); 18 | } else { 19 | return printTree(depModule, depPrefix + " └──"); 20 | } 21 | }); 22 | }; 23 | 24 | module.exports.logger = logger = function() { 25 | return through.obj(function(file, enc, callback) { 26 | console.log(">>", path.relative(process.cwd(), file.path)); 27 | return callback(null, file); 28 | }); 29 | }; 30 | 31 | module.exports.fixModuleName = fixModuleName = function(moduleName) { 32 | return moduleName.replace(/\\/g, '/'); 33 | }; 34 | 35 | }).call(this); 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amd-optimize", 3 | "version": "0.6.1", 4 | "description": "An AMD (i.e. RequireJS) optimizer that's stream-friendly. Made for gulp. (WIP)", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "gulp test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/scalableminds/amd-optimize.git" 12 | }, 13 | "keywords": [ 14 | "gulpplugin", 15 | "gulpfriendly" 16 | ], 17 | "author": "Norman Rzepka (http://scm.io)", 18 | "license": "MIT", 19 | "dependencies": { 20 | "acorn": "^0.9.0", 21 | "ast-types": "~0.3.16", 22 | "async": "~0.2.10", 23 | "escodegen": "^1.4.1", 24 | "lodash": "~2.4.1", 25 | "through2": "~0.4.1", 26 | "vinyl": "^0.2.3", 27 | "vinyl-fs": "^0.3.9", 28 | "vinyl-sourcemaps-apply": "^0.1.4" 29 | }, 30 | "devDependencies": { 31 | "coffee-script": "~1.7.1", 32 | "gulp": "^3.9.0", 33 | "gulp-coffee": "^2.3.1", 34 | "gulp-concat": "^2.3.4", 35 | "gulp-if": "0.0.5", 36 | "gulp-mocha": "^0.4.1", 37 | "gulp-plumber": "^0.6.3", 38 | "gulp-sourcemaps": "^1.1.1", 39 | "gulp-uglify": "~0.2.1", 40 | "gulp-util": "~2.2.14" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/export.coffee: -------------------------------------------------------------------------------- 1 | _ = require("lodash") 2 | b = require("ast-types").builders 3 | escodegen = require("escodegen") 4 | through = require("through2") 5 | path = require("path") 6 | vinylSourcemapsApply = require("vinyl-sourcemaps-apply") 7 | 8 | module.exports = fixModule = (options = {}) -> 9 | 10 | options = _.defaults(options, 11 | wrapShim : true 12 | ) 13 | 14 | through.obj( (module, enc, done) -> 15 | 16 | if module.isShallow 17 | done() 18 | return 19 | 20 | ast = module.file.ast 21 | delete module.file.ast 22 | 23 | if not module.hasDefine 24 | 25 | defineReturnStatement = b.returnStatement( 26 | if module.exports 27 | b.identifier(module.exports) 28 | else 29 | null 30 | ) 31 | 32 | if options.wrapShim and module.isShimmed 33 | defineBody = ast.body.concat([defineReturnStatement]) 34 | else 35 | defineBody = [defineReturnStatement] 36 | 37 | defineCall = b.callExpression( 38 | b.identifier("define") 39 | [ 40 | b.literal(module.name) 41 | b.arrayExpression(module.deps.map( (dep) -> b.literal(dep.name) )) 42 | b.functionExpression( 43 | null 44 | [] 45 | b.blockStatement(defineBody) 46 | ) 47 | ] 48 | ) 49 | 50 | if options.wrapShim and module.isShimmed 51 | ast.body = [b.expressionStatement( 52 | defineCall 53 | )] 54 | 55 | else 56 | ast.body.push( 57 | b.expressionStatement(defineCall) 58 | ) 59 | 60 | else if module.isAnonymous 61 | 62 | # define("foo") 63 | # define(["123"], ->) 64 | 65 | module.astNodes.forEach((astNode) => 66 | if astNode.callee.name == "define" and 67 | ( 68 | astNode.arguments.length == 1 or 69 | (astNode.arguments.length == 2 and astNode.arguments[0].type == "ArrayExpression") 70 | ) 71 | 72 | astNode.arguments = [ 73 | b.literal(module.name) 74 | b.arrayExpression(module.deps.map( (dep) -> b.literal(dep.name) )) 75 | _.last(astNode.arguments) 76 | ] 77 | ) 78 | 79 | if module.hasDefine and module.isShimmed 80 | ast.body = [b.expressionStatement( 81 | b.callExpression( 82 | b.memberExpression( 83 | b.functionExpression( 84 | null 85 | [] 86 | b.blockStatement( 87 | ast.body 88 | ) 89 | ) 90 | b.identifier("call") 91 | false 92 | ) 93 | [b.thisExpression()] 94 | ) 95 | )] 96 | 97 | # TODO: Handle shimmed, mapped and relative deps 98 | 99 | # console.log escodegen.generate(module.file.ast, sourceMap : true).toString() 100 | 101 | 102 | sourceFile = module.file.clone() 103 | sourceFile.sourceMap = module.file.sourceMap 104 | 105 | if sourceFile.sourceMap 106 | generatedCode = escodegen.generate( 107 | ast, 108 | comment: options.preserveComments 109 | sourceMap : true 110 | sourceMapWithCode : true 111 | file : sourceFile.sourceMap.file 112 | ) 113 | 114 | sourceFile.contents = new Buffer(generatedCode.code, "utf8") 115 | vinylSourcemapsApply(sourceFile, generatedCode.map.toJSON()) 116 | 117 | else if not options.preserveFiles 118 | sourceFile = module.file.clone() 119 | sourceFile.contents = new Buffer(escodegen.generate(ast, { comment: options.preserveComments }), "utf8") 120 | 121 | @push(sourceFile) 122 | done() 123 | 124 | ) 125 | -------------------------------------------------------------------------------- /src/index.coffee: -------------------------------------------------------------------------------- 1 | _ = require("lodash") 2 | fs = require("fs") 3 | path = require("path") 4 | vinylFs = require("vinyl-fs") 5 | async = require("async") 6 | through = require("through2") 7 | 8 | Readable = require("stream").Readable 9 | 10 | trace = require("./trace") 11 | exportModule = require("./export") 12 | util = require("./util") 13 | 14 | firstChunk = (stream, callback) -> 15 | 16 | settled = false 17 | stream 18 | .on("data", (data) -> 19 | if not settled 20 | settled = true 21 | callback(null, data) 22 | return 23 | ).on("end", -> 24 | if not settled 25 | callback() 26 | return 27 | ).on("error", (err) -> 28 | if not settled 29 | settled = true 30 | callback(err) 31 | return 32 | ) 33 | return 34 | 35 | 36 | collectModules = (module, omitInline = true) -> 37 | # Depth-first search over the module dependency tree 38 | 39 | outputBuffer = [] 40 | 41 | collector = (currentModule) -> 42 | 43 | currentModule.deps.forEach( (depModule) -> 44 | collector(depModule) 45 | ) 46 | if not (omitInline and currentModule.isInline) and not _.any(outputBuffer, name : currentModule.name) 47 | outputBuffer.push(currentModule) 48 | 49 | collector(module) 50 | 51 | return outputBuffer 52 | 53 | 54 | mergeOptionsFile = (file, options = {}) -> 55 | 56 | return _.merge( 57 | {} 58 | Function(""" 59 | var output, 60 | requirejs = require = function() {}, 61 | define = function () {}; 62 | require.config = function (options) { output = options; }; 63 | #{file.contents.toString("utf8")}; 64 | return output; 65 | """)() 66 | options 67 | ) 68 | 69 | 70 | defaultLoader = (fileBuffer, options) -> 71 | 72 | return (name, callback, asPlainFile) -> 73 | 74 | addJs = (!asPlainFile) and '.js' or '' 75 | 76 | if options.baseUrl and file = _.detect(fileBuffer, path : path.resolve(options.baseUrl, name + addJs)) 77 | callback(null, file) 78 | else if file = _.detect(fileBuffer, relative : path.join(options.baseUrl, name + addJs)) 79 | callback(null, file) 80 | else if options.loader 81 | options.loader(name, callback) 82 | else 83 | module.exports.loader()(path.join(options.baseUrl, name + addJs), callback) 84 | 85 | 86 | 87 | module.exports = rjs = (entryModuleName, options = {}) -> 88 | 89 | # Default options 90 | options = _.defaults( 91 | options, { 92 | baseUrl : "" 93 | configFile : null 94 | exclude : [] 95 | excludeShallow : [] 96 | # include : [] 97 | findNestedDependencies : false 98 | # wrapShim : true 99 | loader : null 100 | preserveComments : false 101 | preserveFiles : false 102 | } 103 | ) 104 | 105 | # Fix sloppy options 106 | if _.isString(options.exclude) 107 | options.exclude = [options.exclude] 108 | 109 | if _.isString(options.excludeShallow) 110 | options.excludeShallow = [options.excludeShallow] 111 | 112 | # Prepare config file stream 113 | if _.isString(options.configFile) or _.isArray(options.configFile) 114 | configFileStream = vinylFs.src(options.configFile) 115 | else if _.isObject(options.configFile) 116 | configFileStream = options.configFile 117 | 118 | fileBuffer = [] 119 | 120 | # Go! 121 | mainStream = through.obj( 122 | # transform 123 | (file, enc, done) -> 124 | fileBuffer.push(file) 125 | done() 126 | 127 | # flush 128 | (done) -> 129 | 130 | async.waterfall([ 131 | 132 | (callback) -> 133 | # Read and merge external options 134 | 135 | if configFileStream 136 | configFileStream.pipe( 137 | through.obj( 138 | (file, enc, done) -> 139 | options = mergeOptionsFile(file, options) 140 | done() 141 | -> callback() 142 | ) 143 | ) 144 | 145 | else 146 | callback() 147 | 148 | (callback) -> 149 | 150 | # Trace entry module 151 | trace(entryModuleName, options, null, defaultLoader(fileBuffer, options), callback) 152 | 153 | (module, callback) -> 154 | 155 | # Flatten modules list 156 | callback(null, collectModules(module)) 157 | 158 | 159 | (modules, callback) -> 160 | 161 | # Find excluded modules 162 | if _.isArray(options.exclude) 163 | async.map( 164 | options.exclude 165 | (moduleName, callback) -> 166 | trace(moduleName, options, null, defaultLoader(fileBuffer, options), callback) 167 | 168 | (err, excludedModules) -> 169 | if err 170 | callback(err) 171 | else 172 | callback(null, modules, _(excludedModules) 173 | .map((module) -> collectModules(module)) 174 | .flatten() 175 | .pluck("name") 176 | .unique() 177 | .value()) 178 | ) 179 | else 180 | callback(null, modules, []) 181 | 182 | 183 | 184 | (modules, excludedModuleNames, callback) -> 185 | # printTree(module) 186 | 187 | # Remove excluded modules 188 | modules = _.reject(modules, (module) -> 189 | return _.contains(excludedModuleNames, module.name) or 190 | _.contains(options.excludeShallow, module.name) 191 | ) 192 | 193 | # Fix and export all the files in correct order 194 | exportStream = exportModule(options) 195 | exportStream 196 | .on("data", (file) -> 197 | mainStream.push(file) 198 | ) 199 | .on("end", -> callback()) 200 | .on("error", callback) 201 | 202 | modules.forEach(exportStream.write.bind(exportStream)) 203 | exportStream.end() 204 | 205 | # Done! 206 | 207 | ], done) 208 | 209 | ) 210 | 211 | return mainStream 212 | 213 | 214 | module.exports.src = (moduleName, options) -> 215 | 216 | source = rjs(moduleName, options) 217 | process.nextTick -> source.end() 218 | return source 219 | 220 | 221 | module.exports.loader = (filenameResolver, pipe) -> 222 | 223 | (moduleName, callback) -> 224 | 225 | # console.log(filenameResolver(moduleName)) 226 | if filenameResolver 227 | filename = filenameResolver(moduleName) 228 | else 229 | filename = moduleName 230 | 231 | source = vinylFs.src(filename).pipe(through.obj()) 232 | 233 | if pipe 234 | source = source.pipe(pipe()) 235 | 236 | firstChunk(source, callback) 237 | return 238 | -------------------------------------------------------------------------------- /src/parse.coffee: -------------------------------------------------------------------------------- 1 | _ = require("lodash") 2 | acorn = require("acorn") 3 | escodegen = require("escodegen") 4 | walk = require("acorn/util/walk") 5 | 6 | valuesFromArrayExpression = (expr) -> expr.elements.map( (a) -> a.value ) 7 | 8 | module.exports = parseRequireDefinitions = (config, file, callback) -> 9 | 10 | try 11 | if config.preserveComments 12 | comments = [] 13 | tokens = [] 14 | ast = acorn.parse( 15 | file.stringContents, 16 | sourceFile : file.relative 17 | locations : file.sourceMap? 18 | ranges: true 19 | onComment: comments 20 | onToken: tokens 21 | ) 22 | escodegen.attachComments(ast, comments, tokens) 23 | else 24 | ast = acorn.parse( 25 | file.stringContents, 26 | sourceFile : file.relative, 27 | locations : file.sourceMap? 28 | ) 29 | catch err 30 | if err instanceof SyntaxError 31 | err.filename = file.path 32 | err.message += " in #{file.path}" 33 | callback(err) 34 | return 35 | 36 | file.ast = ast 37 | 38 | definitions = [] 39 | walk.ancestor(ast, CallExpression : (node, state) -> 40 | 41 | if node.callee.name == "define" 42 | 43 | switch node.arguments.length 44 | 45 | when 1 46 | # define(function (require, exports, module) {}) 47 | if node.arguments[0].type == "FunctionExpression" and 48 | node.arguments[0].params.length > 0 49 | 50 | deps = ['require', 'exports', 'module'] 51 | walk.simple(node.arguments[0], CallExpression : (node) -> 52 | if node.callee.name == "require" or node.callee.name == "requirejs" 53 | deps.push(node.arguments[0].value) 54 | ) 55 | 56 | when 2 57 | switch node.arguments[0].type 58 | when "Literal" 59 | # define("name", function () {}) 60 | moduleName = node.arguments[0].value 61 | when "ArrayExpression" 62 | # define(["dep"], function () {}) 63 | deps = valuesFromArrayExpression(node.arguments[0]) 64 | 65 | when 3 66 | # define("name", ["dep"], function () {}) 67 | moduleName = node.arguments[0].value 68 | deps = valuesFromArrayExpression(node.arguments[1]) 69 | 70 | definitions.push( 71 | method : "define" 72 | moduleName : moduleName 73 | deps : deps ? [] 74 | argumentsLength: node.arguments.length 75 | node : node 76 | ) 77 | 78 | isInsideDefine = true 79 | 80 | 81 | if (node.callee.name == "require" or node.callee.name == "requirejs") and node.arguments.length > 0 and node.arguments[0].type == "ArrayExpression" 82 | 83 | defineAncestors = _.any( 84 | state.slice(0, -1) 85 | (ancestorNode) -> ancestorNode.type == "CallExpression" and (ancestorNode.callee.name == "define" or ancestorNode.callee.name == "require" or ancestorNode.callee.name == "requirejs") 86 | ) 87 | if config.findNestedDependencies or not defineAncestors 88 | definitions.push( 89 | method : "require" 90 | moduleName : undefined 91 | deps : valuesFromArrayExpression(node.arguments[0]) 92 | node : node 93 | ) 94 | 95 | ) 96 | 97 | callback(null, file, definitions) 98 | return 99 | -------------------------------------------------------------------------------- /src/trace.coffee: -------------------------------------------------------------------------------- 1 | _ = require("lodash") 2 | fs = require("fs") 3 | path = require("path") 4 | async = require("async") 5 | VinylFile = require("vinyl") 6 | 7 | parse = require("./parse") 8 | util = require("./util") 9 | 10 | 11 | class Module 12 | constructor : (@name, @file, @deps = []) -> 13 | 14 | @name = util.fixModuleName(@name) 15 | @isShallow = false 16 | @isShimmed = false 17 | @isAnonymous = false 18 | @isInline = false 19 | @hasDefine = false 20 | @astNodes = [] 21 | 22 | 23 | 24 | module.exports = traceModule = (startModuleName, config, allModules = [], fileLoader, callback) -> 25 | 26 | foundModuleNames = [] 27 | textFiles = {} 28 | jsonFiles = {} 29 | 30 | resolveModuleName = (moduleName, relativeTo = "") -> 31 | 32 | isText = (moduleName.indexOf('text!') != -1) 33 | 34 | # get rid of text! prefix 35 | if (isText) 36 | moduleName = moduleName.replace('text!', '') 37 | 38 | isJson = (moduleName.indexOf('json!') != -1) 39 | 40 | # get rid of text! prefix 41 | if (isJson) 42 | moduleName = moduleName.replace('json!', '') 43 | 44 | # deal with module path prefixes 45 | if config.paths and !config.paths[moduleName] 46 | slashIdx = moduleName.indexOf("/") 47 | if slashIdx > 0 48 | eligiblePath = config.paths[moduleName.substr(0, slashIdx)]; 49 | if eligiblePath 50 | moduleName = eligiblePath + moduleName.substr(slashIdx) 51 | 52 | relativeToFileName = resolveModuleFileName(relativeTo) 53 | 54 | if moduleName[0] == "." 55 | moduleName = util.fixModuleName(path.join(path.dirname(relativeToFileName), moduleName)) 56 | 57 | if config.map and config.map[relativeTo] and config.map[relativeTo][moduleName] 58 | moduleName = config.map[relativeTo][moduleName] 59 | 60 | # add resolved name to list of text files 61 | if (isText) 62 | textFiles[moduleName] = true 63 | 64 | # add resolved name to list of json files 65 | if (isJson) 66 | jsonFiles[moduleName] = true 67 | 68 | return moduleName 69 | 70 | 71 | resolveModuleFileName = (moduleName) -> 72 | 73 | if config.paths and config.paths[moduleName] 74 | moduleName = config.paths[moduleName] 75 | 76 | if /!|^exports$|^require$|^module$|^empty:/.test(moduleName) 77 | return 78 | else 79 | return moduleName 80 | 81 | 82 | 83 | resolveModules = (moduleNames, callback) -> 84 | 85 | async.mapSeries(moduleNames, resolveModule, callback) 86 | return 87 | 88 | 89 | resolveInlinedModule = (moduleName, deps, astNode, vinylFile, callback) -> 90 | 91 | async.waterfall([ 92 | 93 | (callback) -> resolveModules(deps, callback) 94 | 95 | (modules, callback) -> 96 | module = new Module(moduleName, vinylFile, _.compact(modules)) 97 | module.hasDefine = true 98 | module.isInline = true 99 | module.astNodes.push(astNode) 100 | emitModule(module) 101 | callback() 102 | 103 | ], callback) 104 | return 105 | 106 | 107 | resolveModule = (moduleName, callback) -> 108 | 109 | module = _.detect(allModules, name : moduleName) 110 | if module 111 | callback(null, module) 112 | return 113 | 114 | fileName = resolveModuleFileName(moduleName) 115 | if not fileName 116 | module = new Module(moduleName) 117 | module.isShallow = true 118 | callback(null, emitModule(module)) 119 | return 120 | 121 | if _.contains(foundModuleNames, moduleName) 122 | callback(new Error("Circular dependency detected. Module '#{moduleName}' has been processed before.")) 123 | return 124 | else 125 | foundModuleNames.push(moduleName) 126 | 127 | module = null 128 | isTextFile = !!textFiles[moduleName] 129 | 130 | isJsonFile = !!jsonFiles[moduleName] 131 | 132 | # console.log("Resolving", moduleName, fileName) 133 | 134 | async.waterfall([ 135 | 136 | (callback) -> 137 | fileLoader(fileName, callback, isTextFile || isJsonFile) 138 | 139 | (file, callback) -> 140 | 141 | if arguments.length == 1 142 | callback = file 143 | file = null 144 | 145 | if file 146 | callback(null, file) 147 | else 148 | callback(new Error("No file for module '#{moduleName}' found.")) 149 | 150 | (file, callback) -> 151 | 152 | file.stringContents = file.contents.toString("utf8") 153 | 154 | if (isTextFile) 155 | file.stringContents = 'define(function(){ return ' + JSON.stringify(file.stringContents) + '; });' 156 | 157 | if (isJsonFile) 158 | file.stringContents = 'define(function(){ return JSON.parse(' + JSON.stringify(file.stringContents) + '); });'; 159 | 160 | module = new Module(moduleName, file) 161 | callback(null, file) 162 | 163 | parse.bind(null, config) 164 | 165 | (file, definitions, callback) -> 166 | 167 | if _.filter(definitions, (def) -> return def.method == "define" and def.moduleName == undefined and 0 < def.argumentsLength < 3).length > 1 168 | callback(new Error("A module must not have more than one anonymous 'define' calls.")) 169 | return 170 | 171 | 172 | module.hasDefine = _.any(definitions, (def) -> 173 | return def.method == "define" and (def.moduleName == undefined or def.moduleName == moduleName) 174 | ) 175 | 176 | async.mapSeries( 177 | definitions 178 | (def, callback) -> 179 | 180 | def.deps = def.deps.map( (depName) -> resolveModuleName(depName, def.moduleName ? moduleName) ) 181 | 182 | if def.method == "define" and def.moduleName != undefined and def.moduleName != moduleName 183 | async.waterfall([ 184 | (callback) -> resolveInlinedModule(def.moduleName, def.deps, def.node, file, callback) 185 | (callback) -> callback(null, []) 186 | ], callback) 187 | 188 | else 189 | module.astNodes.push(def.node) 190 | resolveModules(def.deps, callback) 191 | return 192 | callback 193 | ) 194 | 195 | 196 | (unflatModules, callback) -> 197 | 198 | callback(null, _.compact(_.flatten(unflatModules))) 199 | 200 | 201 | (depModules, callback) -> 202 | 203 | module.deps.push(depModules...) 204 | module.isAnonymous = true 205 | 206 | async.waterfall([ 207 | 208 | (callback) -> 209 | 210 | additionalDepNames = null 211 | 212 | if config.shim and shim = config.shim[module.name] 213 | 214 | if module.hasDefine 215 | console.log("[warn]", "Module '#{module.name}' is shimmed even though it has a proper define.") 216 | 217 | module.isShimmed = true 218 | 219 | if shim.exports 220 | module.exports = shim.exports 221 | 222 | if _.isArray(shim) 223 | additionalDepNames = shim 224 | else if shim.deps 225 | additionalDepNames = shim.deps 226 | 227 | if additionalDepNames 228 | resolveModules(additionalDepNames, callback) 229 | else 230 | callback(null, []) 231 | 232 | 233 | (depModules, callback) -> 234 | 235 | module.deps.push(depModules...) 236 | callback(null, emitModule(module)) 237 | 238 | ], callback) 239 | return 240 | 241 | ], callback) 242 | return 243 | 244 | 245 | emitModule = (module) -> 246 | 247 | if not _.any(allModules, name : module.name) 248 | allModules.push(module) 249 | return module 250 | 251 | 252 | resolveModule(startModuleName, callback) 253 | 254 | return 255 | 256 | -------------------------------------------------------------------------------- /src/util.coffee: -------------------------------------------------------------------------------- 1 | path = require("path") 2 | through = require("through2") 3 | 4 | module.exports.printTree = printTree = (currentModule, prefix = "") -> 5 | 6 | console.log(prefix, currentModule.name, "(#{path.relative(process.cwd(), currentModule.file.relative)})") 7 | 8 | depPrefix = prefix 9 | .replace("├", "|") 10 | .replace("└", " ") 11 | .replace(/─/g, " ") 12 | currentModule.deps.forEach((depModule, i) -> 13 | 14 | if i + 1 < currentModule.deps.length 15 | printTree(depModule, "#{depPrefix} ├──") 16 | else 17 | printTree(depModule, "#{depPrefix} └──") 18 | ) 19 | 20 | 21 | module.exports.logger = logger = -> 22 | return through.obj((file, enc, callback) -> 23 | console.log(">>", path.relative(process.cwd(), file.path)) 24 | callback(null, file) 25 | ) 26 | 27 | 28 | module.exports.fixModuleName = fixModuleName = (moduleName) -> 29 | return moduleName.replace(/\\/g, '/') 30 | -------------------------------------------------------------------------------- /test/expected/comment.js.map.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ "comment.js" ], 4 | "file": "comment.js", 5 | "names": [ "define", "call" ], 6 | "mappings" : "AACA;AAAA,CAAC,YAAY;AAAA,IACTA,MAAA,CAAO,SAAP,EAAkB,EAAlB,EAAsB,KAAtB,EADS;AAAA,CAAZ,CAECC,IAFF,CAEO,IAFP", 7 | "sourcesContent": [ "// Generated by CoffeeScript 1.7.1\n(function () {\n define('comment', [], 'FOO');\n}.call(this));" ] 8 | } 9 | -------------------------------------------------------------------------------- /test/expected/foo.js.map.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ "foo.js" ], 4 | "file": "foo.js", 5 | "names": [ "define", "call" ], 6 | "mappings": "AACA,aAAY;AAAA,IACVA,MAAA,C,KAAA,E,EAAA,EAAO,KAAP,EADU;AAAA,CAAZ,CAGGC,IAHH,CAGQ,IAHR", 7 | "sourcesContent": [ "// Generated by CoffeeScript 1.7.1\n(function() {\n define(\"FOO\");\n\n}).call(this);\n" ] 8 | } 9 | -------------------------------------------------------------------------------- /test/expected/index.js.map.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ "index.js" ], 4 | "file": "index.js", 5 | "names": [ "define", "foo", "console", "log", "call" ], 6 | "mappings": "AACA,aAAY;AAAA,IACVA,MAAA,C,OAAA,E,OAAA,EAAgB,UAASC,GAAT,EAAc;AAAA,QAC5BC,OAAA,CAAQC,GAAR,CAAYF,GAAZ,EAD4B;AAAA,QAE5B,SAF4B;AAAA,KAA9B,EADU;AAAA,CAAZ,CAMGG,IANH,CAMQ,IANR", 7 | "sourcesContent": [ "// Generated by CoffeeScript 1.7.1\n(function() {\n define([\"foo\"], function(foo) {\n console.log(foo);\n debugger;\n });\n\n}).call(this);\n" ] 8 | } 9 | -------------------------------------------------------------------------------- /test/expected/test.json.js: -------------------------------------------------------------------------------- 1 | define('test.json', [], function () { 2 | return JSON.parse('{"hello": "world"}'); 3 | }); 4 | -------------------------------------------------------------------------------- /test/expected/text.html.js: -------------------------------------------------------------------------------- 1 | define('text.html', [], function () { 2 | return '

This is text

'; 3 | }); 4 | -------------------------------------------------------------------------------- /test/fixtures/comments/comment.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.7.1 2 | (function () { 3 | define('comment', [], 'FOO'); 4 | }.call(this)); -------------------------------------------------------------------------------- /test/fixtures/commonjs/bar.js: -------------------------------------------------------------------------------- 1 | define(['exports', 'module'], function(exports, module) { 2 | exports.baz = function() { 3 | return module.id; 4 | }; 5 | }); -------------------------------------------------------------------------------- /test/fixtures/commonjs/faz.js: -------------------------------------------------------------------------------- 1 | define(function(require) { 2 | require('./bar'); 3 | return {}; 4 | }); 5 | -------------------------------------------------------------------------------- /test/fixtures/commonjs/foo.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | var bar = require('./bar'); 3 | module.exports = function () { 4 | return bar.baz(); 5 | }; 6 | }); 7 | -------------------------------------------------------------------------------- /test/fixtures/commonjs/fuz.js: -------------------------------------------------------------------------------- 1 | define(function(require) { 2 | var bar = require('./bar'); 3 | return bar.baz(); 4 | }); 5 | -------------------------------------------------------------------------------- /test/fixtures/config/config.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | require.config({ 4 | paths : { 5 | foo : "empty:" 6 | } 7 | }); 8 | 9 | require([ "foo" ], function(foo) {}); 10 | 11 | })(); -------------------------------------------------------------------------------- /test/fixtures/config/index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | define(["foo"], function (test) { 3 | console.log(test); 4 | }); 5 | })(); -------------------------------------------------------------------------------- /test/fixtures/core/bar.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | define("TEST2"); 3 | 4 | })(); 5 | -------------------------------------------------------------------------------- /test/fixtures/core/duu.js: -------------------------------------------------------------------------------- 1 | define(["./fuz/ahah"], function (ahah) {}); -------------------------------------------------------------------------------- /test/fixtures/core/duz.js: -------------------------------------------------------------------------------- 1 | define(["fuu/ahah"], function (ahah) {}); 2 | -------------------------------------------------------------------------------- /test/fixtures/core/foo.coffee: -------------------------------------------------------------------------------- 1 | define("FOO") -------------------------------------------------------------------------------- /test/fixtures/core/foo.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.7.1 2 | (function() { 3 | define("FOO"); 4 | 5 | }).call(this); 6 | -------------------------------------------------------------------------------- /test/fixtures/core/fuz/ahah.js: -------------------------------------------------------------------------------- 1 | define(function() { return "AHAH"; }); -------------------------------------------------------------------------------- /test/fixtures/core/index.coffee: -------------------------------------------------------------------------------- 1 | define(["foo"], (foo) -> 2 | console.log(foo) 3 | debugger 4 | ) 5 | -------------------------------------------------------------------------------- /test/fixtures/core/index.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.7.1 2 | (function() { 3 | define(["foo"], function(foo) { 4 | console.log(foo); 5 | debugger; 6 | }); 7 | 8 | }).call(this); 9 | -------------------------------------------------------------------------------- /test/fixtures/core/inline.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | define("test", ["foo"], function (foo) { 3 | console.log(foo); 4 | }); 5 | 6 | define(["bar", "test"], function (bar) { 7 | console.log(bar); 8 | }); 9 | })(); -------------------------------------------------------------------------------- /test/fixtures/core/nested.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | define(["foo"], function (test) { 3 | require(["bar"], function () {}); 4 | }); 5 | })(); -------------------------------------------------------------------------------- /test/fixtures/core/nested_requirejs.js: -------------------------------------------------------------------------------- 1 | /** 2 | HALLO 3 | **/ 4 | (function () { 5 | define(["foo"], function (test) { 6 | requirejs(["bar"], function () {}); 7 | }); 8 | })(); 9 | -------------------------------------------------------------------------------- /test/fixtures/core/plugin-json.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | define(["bar", "json!./test.json"], function (bar, test) { 3 | console.log(bar, test); 4 | }); 5 | })(); -------------------------------------------------------------------------------- /test/fixtures/core/plugin-text.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | define(["bar", "text!./text.html"], function (bar, test) { 3 | console.log(bar, test); 4 | }); 5 | })(); -------------------------------------------------------------------------------- /test/fixtures/core/plugin.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | define(["bar", "plugin!foo"], function (test) { 3 | console.log(test); 4 | }); 5 | })(); -------------------------------------------------------------------------------- /test/fixtures/core/relative.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | define(["./foo"], function (test) { 3 | console.log(test); 4 | }); 5 | })(); -------------------------------------------------------------------------------- /test/fixtures/core/require_exports.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | define(["bar", "require", "exports"], function (test, require, exports) { 3 | require("foo") 4 | console.log(test); 5 | }); 6 | })(); -------------------------------------------------------------------------------- /test/fixtures/core/test.json: -------------------------------------------------------------------------------- 1 | {"hello": "world"} -------------------------------------------------------------------------------- /test/fixtures/core/text.html: -------------------------------------------------------------------------------- 1 |

This is text

-------------------------------------------------------------------------------- /test/fixtures/deps/config.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | require.config({ 5 | paths: { 6 | extra: '../deps/extra/extra' 7 | } 8 | }); 9 | }()); 10 | -------------------------------------------------------------------------------- /test/fixtures/deps/deps/extra/extra.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | define(['./lib/helper'], function (helper) { 5 | return 'extra:' + helper; 6 | }); 7 | }()); 8 | -------------------------------------------------------------------------------- /test/fixtures/deps/deps/extra/lib/helper.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | define(function () { 5 | return 'helper'; 6 | }); 7 | }()); 8 | -------------------------------------------------------------------------------- /test/fixtures/deps/src/index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | define(['extra'], function (extra) { 5 | return 'hello:' + extra; 6 | }); 7 | }()); 8 | -------------------------------------------------------------------------------- /test/fixtures/errors/cycle1.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | define(["./cycle2"], function (test) { 3 | console.log(test); 4 | }); 5 | })(); -------------------------------------------------------------------------------- /test/fixtures/errors/cycle2.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | define(["./cycle1"], function (test) { 3 | console.log(test); 4 | }); 5 | })(); -------------------------------------------------------------------------------- /test/fixtures/errors/cycle_exports1.js: -------------------------------------------------------------------------------- 1 | define(['cycle_exports2', 'exports'], function(cycle2, exports) { 2 | exports.bar = function () { 3 | return cycle2.fuu; 4 | }; 5 | }); 6 | -------------------------------------------------------------------------------- /test/fixtures/errors/cycle_exports2.js: -------------------------------------------------------------------------------- 1 | define(['cycle_exports1', 'exports'], function(cycle1, exports) { 2 | exports.foo = function () { 3 | return cycle1.bar(); 4 | }; 5 | exports.fuu = "TEST"; 6 | }); 7 | -------------------------------------------------------------------------------- /test/fixtures/errors/foo.coffee: -------------------------------------------------------------------------------- 1 | define("FOO) -------------------------------------------------------------------------------- /test/fixtures/errors/foo.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | define("FOO"; 3 | }).call(this); 4 | -------------------------------------------------------------------------------- /test/fixtures/errors/multiple_anonymous_defines.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | define(function (test) { 3 | define(function () { 4 | }); 5 | }); 6 | })(); 7 | -------------------------------------------------------------------------------- /test/fixtures/errors/return.js: -------------------------------------------------------------------------------- 1 | define(function () { return "test"; }); 2 | return; 3 | -------------------------------------------------------------------------------- /test/fixtures/inexclude/bar.js: -------------------------------------------------------------------------------- 1 | define(["baz"], function (baz) {}); -------------------------------------------------------------------------------- /test/fixtures/inexclude/baz.js: -------------------------------------------------------------------------------- 1 | define(function () {}); 2 | -------------------------------------------------------------------------------- /test/fixtures/inexclude/foo.js: -------------------------------------------------------------------------------- 1 | define(["baz"], function (baz) {}); -------------------------------------------------------------------------------- /test/fixtures/shim/amd.js: -------------------------------------------------------------------------------- 1 | var test = "TEST"; 2 | define(test); -------------------------------------------------------------------------------- /test/fixtures/shim/index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | define(["no_amd"], function (test) { 3 | console.log(test); 4 | }); 5 | })(); -------------------------------------------------------------------------------- /test/fixtures/shim/no_amd.js: -------------------------------------------------------------------------------- 1 | var test = "TEST"; -------------------------------------------------------------------------------- /test/fixtures/shim/no_amd2.js: -------------------------------------------------------------------------------- 1 | var test2 = "TEST2"; -------------------------------------------------------------------------------- /test/fixtures/shim/no_shim.js: -------------------------------------------------------------------------------- 1 | define("app", [], function () { return "I am an application object" }); 2 | 3 | require(["app"], function () {}); -------------------------------------------------------------------------------- /test/fixtures/shortcuts/config.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | "use strict"; 3 | 4 | require.config({ 5 | paths: { 6 | "module": "path/to/module" 7 | } 8 | }); 9 | }()); 10 | -------------------------------------------------------------------------------- /test/fixtures/shortcuts/index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | "use strict"; 3 | 4 | define(["module/foo"], function (foo) { 5 | return "hello:" + foo; 6 | }); 7 | }()); 8 | -------------------------------------------------------------------------------- /test/fixtures/shortcuts/path/to/module/foo.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | define("foo", function(){ 3 | return "foo"; 4 | }); 5 | })(); 6 | -------------------------------------------------------------------------------- /test/index_test.coffee: -------------------------------------------------------------------------------- 1 | _ = require("lodash") 2 | assert = require("assert") 3 | path = require("path") 4 | fs = require("fs") 5 | util = require("util") 6 | vinylfs = require("vinyl-fs") 7 | coffee = require("gulp-coffee") 8 | concat = require("gulp-concat") 9 | plumber = require("gulp-plumber") 10 | sourcemaps = require("gulp-sourcemaps") 11 | acorn = require("acorn") 12 | walk = require("acorn/util/walk") 13 | 14 | amdOptimize = require("../lib/index") 15 | 16 | dir = path.relative(process.cwd(), __dirname) 17 | 18 | 19 | checkExpectedFiles = (expectedFiles, stream, done) -> 20 | 21 | expectedFiles = expectedFiles.slice(0) 22 | stream 23 | .on("data", (file) -> 24 | assert.equal(path.normalize(expectedFiles.shift()), file.relative) 25 | ) 26 | .on("end", -> 27 | assert.equal(expectedFiles.length, 0) 28 | done() 29 | ) 30 | 31 | checkAst = (expectedFile, stream, tester, done) -> 32 | 33 | foundFile = false 34 | stream 35 | .on("data", (file) -> 36 | if file.relative == path.normalize(expectedFile) 37 | foundFile = true 38 | stringContents = file.contents.toString("utf8") 39 | ast = acorn.parse(stringContents) 40 | tester(ast, stringContents) 41 | ) 42 | .on("end", -> 43 | assert.ok(foundFile, "Expected file '#{expectedFile}' not found.") 44 | done() 45 | ) 46 | 47 | 48 | describe "core", -> 49 | 50 | it "should work without configuration", (done) -> 51 | 52 | checkExpectedFiles( 53 | ["foo.js", "index.js"] 54 | vinylfs.src("#{dir}/fixtures/core/*.js") 55 | .pipe(amdOptimize("index")) 56 | done 57 | ) 58 | 59 | 60 | it "should work with relative dependencies", (done) -> 61 | 62 | checkExpectedFiles( 63 | ["foo.js", "relative.js"] 64 | vinylfs.src("#{dir}/fixtures/core/*.js") 65 | .pipe(amdOptimize("relative")) 66 | done 67 | ) 68 | 69 | 70 | it "should work with inline dependencies", (done) -> 71 | 72 | expectedFiles = ["foo.js", "bar.js", "inline.js"] 73 | 74 | counter = 0 75 | vinylfs.src("#{dir}/fixtures/core/*.js") 76 | .pipe(amdOptimize("inline")) 77 | .on("data", (file) -> 78 | if counter < 2 79 | assert(_.contains(expectedFiles[0..1], file.relative)) 80 | else 81 | assert.equal(expectedFiles[2], file.relative) 82 | counter++ 83 | ) 84 | .on("end", -> 85 | assert.equal(counter, 3) 86 | done() 87 | ) 88 | 89 | it "should work with `paths` config", (done) -> 90 | 91 | checkExpectedFiles( 92 | ["bar.js", "index.js"] 93 | 94 | vinylfs.src("#{dir}/fixtures/core/*.js") 95 | .pipe(amdOptimize( 96 | "index" 97 | paths : { 98 | foo : "bar" 99 | } 100 | )) 101 | 102 | done 103 | ) 104 | 105 | 106 | it "should work with `map` config", (done) -> 107 | 108 | checkExpectedFiles( 109 | ["bar.js", "index.js"] 110 | 111 | vinylfs.src("#{dir}/fixtures/core/*.js") 112 | .pipe(amdOptimize( 113 | "index" 114 | map : { 115 | index : { 116 | foo : "bar" 117 | } 118 | } 119 | )) 120 | 121 | done 122 | ) 123 | 124 | it "should work with `map` config for renamed modules (`paths`)", (done) -> 125 | 126 | checkExpectedFiles( 127 | ["bar.js", "duz.js", "index.js"] 128 | 129 | vinylfs.src("#{dir}/fixtures/core/**/*.js") 130 | .pipe(amdOptimize( 131 | "index" 132 | paths : { 133 | foo : "duz" 134 | } 135 | map : { 136 | foo : { 137 | "fuu/ahah" : "bar" 138 | } 139 | } 140 | )) 141 | 142 | done 143 | ) 144 | 145 | it "should only use forward slashes in module names", (done) -> 146 | 147 | checkAst( 148 | "fuz/ahah.js" 149 | vinylfs.src("#{dir}/fixtures/core/**/*.js") 150 | .pipe(amdOptimize("duu")) 151 | (ast) -> 152 | walk.simple(ast, CallExpression : (node) -> 153 | assert.equal(node.arguments[0].value, "fuz/ahah") 154 | ) 155 | done 156 | ) 157 | 158 | 159 | it "should keep the relative paths", (done) -> 160 | 161 | checkExpectedFiles( 162 | ["fuz/ahah.js", "duu.js"] 163 | vinylfs.src("#{dir}/fixtures/core/**/*.js") 164 | .pipe(amdOptimize("duu")) 165 | done 166 | ) 167 | 168 | 169 | it "should remove the ast property when done", (done) -> 170 | 171 | vinylfs.src("#{dir}/fixtures/core/*.js") 172 | .pipe(amdOptimize("index")) 173 | .on("data", (file) -> 174 | assert(not ("ast" in file)) 175 | ) 176 | .on("end", done) 177 | 178 | 179 | it "should make anonymous modules explicitly-named", (done) -> 180 | 181 | checkAst( 182 | "foo.js" 183 | vinylfs.src("#{dir}/fixtures/core/foo.js") 184 | .pipe(amdOptimize("foo")) 185 | (ast) -> 186 | walk.simple(ast, CallExpression : (node) -> 187 | if node.callee.name == "define" 188 | # module name 189 | assert.equal(node.arguments[0].value, "foo") 190 | # dependency declarations 191 | assert.equal(node.arguments[1].elements.length, 0) 192 | # value 193 | assert.equal(node.arguments[2].value, "FOO") 194 | ) 195 | done 196 | ) 197 | 198 | 199 | it "should trace relative dependencies of `path`-configured modules", (done) -> 200 | 201 | checkExpectedFiles( 202 | ["../deps/extra/lib/helper.js", "../deps/extra/extra.js", "index.js"] 203 | vinylfs.src(["#{dir}/fixtures/deps/**/*.js"], { base: "#{dir}/fixtures/deps/src" }) 204 | .pipe(amdOptimize( 205 | "index" 206 | configFile : "#{dir}/fixtures/deps/config.js" 207 | )) 208 | done 209 | ) 210 | 211 | it "should preserve the original files", (done) -> 212 | 213 | vinylfs.src("#{dir}/fixtures/core/*.js") 214 | .pipe(amdOptimize( 215 | "bar" 216 | preserveFiles: true 217 | )) 218 | .on("data", (file) -> 219 | assert.equal( 220 | file.contents.toString("utf8"), 221 | fs.readFileSync("#{dir}/fixtures/core/bar.js", "utf8")) 222 | ) 223 | .on("end", done) 224 | 225 | 226 | describe "src", -> 227 | 228 | it "should work with a default file loader", (done) -> 229 | 230 | checkExpectedFiles( 231 | ["foo.js", "index.js"] 232 | amdOptimize.src( 233 | "index" 234 | baseUrl : "test/fixtures/core" 235 | ) 236 | done 237 | ) 238 | 239 | 240 | it "should work with a default file loader and keep the relative file names" #, (done) -> 241 | 242 | # checkExpectedFiles( 243 | # ["fuz/ahah.js", "duu.js"] 244 | # amdOptimize.src( 245 | # "duu" 246 | # baseUrl : "test/fixtures/core" 247 | # ) 248 | # done 249 | # ) 250 | 251 | 252 | it "should work with a custom file loader", (done) -> 253 | 254 | checkExpectedFiles( 255 | ["foo.js", "index.js"] 256 | amdOptimize.src( 257 | "index" 258 | loader : amdOptimize.loader((name) -> "#{dir}/fixtures/core/#{name}.js") 259 | ) 260 | done 261 | ) 262 | 263 | 264 | it "should work with a custom file loader with a pipe", (done) -> 265 | 266 | checkExpectedFiles( 267 | ["foo.js", "index.js"] 268 | amdOptimize.src( 269 | "index" 270 | loader : amdOptimize.loader( 271 | (name) -> "#{dir}/fixtures/core/#{name}.coffee" 272 | -> coffee() 273 | ) 274 | ) 275 | done 276 | ) 277 | 278 | it "should look for files, if not piped in", (done) -> 279 | 280 | checkExpectedFiles( 281 | ["foo.js", "index.js"] 282 | vinylfs.src("#{dir}/fixtures/core/index.js") 283 | .pipe(amdOptimize( 284 | "index" 285 | baseUrl : "#{dir}/fixtures/core" 286 | )) 287 | done 288 | ) 289 | 290 | 291 | describe "include + exclude", -> 292 | 293 | it "should exclude modules and their dependency tree", (done) -> 294 | 295 | checkExpectedFiles( 296 | ["foo.js"] 297 | vinylfs.src("#{dir}/fixtures/inexclude/*.js") 298 | .pipe(amdOptimize( 299 | "foo" 300 | exclude : ["bar"] 301 | )) 302 | done 303 | ) 304 | 305 | it "should shallowly exclude modules", (done) -> 306 | 307 | checkExpectedFiles( 308 | ["foo.js"] 309 | vinylfs.src("#{dir}/fixtures/inexclude/*.js") 310 | .pipe(amdOptimize( 311 | "foo" 312 | excludeShallow : ["baz"] 313 | )) 314 | done 315 | ) 316 | 317 | it "should include modules even if they had been excluded" 318 | 319 | it "should include other modules" 320 | 321 | 322 | describe "shim", -> 323 | 324 | it "should add a `define` for non-AMD modules", (done) -> 325 | 326 | vinylfs.src("#{dir}/fixtures/shim/*.js") 327 | .pipe(amdOptimize( 328 | "index" 329 | )) 330 | .on("data", (file) -> 331 | if file.relative == "no_amd.js" 332 | stringContents = file.contents.toString("utf8") 333 | assert(/define\(\s*["']no_amd['"]\s*/.test(stringContents)) 334 | ) 335 | .on("end", done) 336 | 337 | 338 | it "should add shimmed dependencies `define` for non-AMD modules", (done) -> 339 | 340 | vinylfs.src("#{dir}/fixtures/shim/*.js") 341 | .pipe(amdOptimize( 342 | "index" 343 | shim : { 344 | no_amd : { 345 | deps : ["no_amd2"] 346 | } 347 | } 348 | )) 349 | .on("data", (file) -> 350 | if file.relative == "no_amd.js" 351 | stringContents = file.contents.toString("utf8") 352 | assert(/define\(\s*["'].*['"]\s*,\s*\[\s*["']no_amd2["']\s*\]/.test(stringContents)) 353 | ) 354 | .on("end", done) 355 | 356 | 357 | it "should add shimmed export for non-AMD modules", (done) -> 358 | 359 | exportVariable = "test" 360 | 361 | vinylfs.src("#{dir}/fixtures/shim/*.js") 362 | .pipe(amdOptimize( 363 | "index" 364 | shim : { 365 | no_amd : { 366 | exports : exportVariable 367 | } 368 | } 369 | )) 370 | .on("data", (file) -> 371 | if file.relative == "no_amd.js" 372 | stringContents = file.contents.toString("utf8") 373 | ast = acorn.parse(stringContents) 374 | 375 | hasDefine = false 376 | walk.simple(ast, CallExpression : (node) -> 377 | 378 | if node.callee.name == "define" 379 | 380 | hasDefine = true 381 | 382 | funcNode = _.last(node.arguments) 383 | assert.equal(funcNode.type, "FunctionExpression") 384 | 385 | returnNode = _.last(funcNode.body.body) 386 | assert.equal(returnNode.type, "ReturnStatement") 387 | 388 | assert.equal(returnNode.argument.type, "Identifier") 389 | assert.equal(returnNode.argument.name, exportVariable) 390 | 391 | ) 392 | assert(hasDefine) 393 | ) 394 | .on("end", done) 395 | 396 | 397 | it "should wrap non-AMD modules with a `define` call", (done) -> 398 | 399 | exportVariable = "test" 400 | 401 | vinylfs.src("#{dir}/fixtures/shim/*.js") 402 | .pipe(amdOptimize( 403 | "no_amd" 404 | wrapShim : true 405 | shim : { 406 | no_amd : { 407 | exports : exportVariable 408 | } 409 | } 410 | )) 411 | .on("data", (file) -> 412 | if file.relative == "no_amd.js" 413 | stringContents = file.contents.toString("utf8") 414 | ast = acorn.parse(stringContents) 415 | 416 | assert.equal(ast.body[0].expression.type, "CallExpression") 417 | assert.equal(ast.body[0].expression.callee.name, "define") 418 | ) 419 | .on("end", done) 420 | 421 | 422 | it "should wrap shimmed AMD modules with an immediately invoked function", (done) -> 423 | 424 | exportVariable = "test" 425 | 426 | vinylfs.src("#{dir}/fixtures/shim/*.js") 427 | .pipe(amdOptimize( 428 | "amd" 429 | wrapShim : true 430 | shim : { 431 | amd : {} 432 | } 433 | )) 434 | .on("data", (file) -> 435 | 436 | if file.relative == "amd.js" 437 | stringContents = file.contents.toString("utf8") 438 | ast = acorn.parse(stringContents) 439 | 440 | assert.equal(ast.body[0].expression.type, "CallExpression") 441 | assert.equal(ast.body[0].expression.callee.name, undefined) 442 | assert.equal(ast.body.length, 1) 443 | ) 444 | .on("end", done) 445 | 446 | 447 | it "should not wrap non-shimmed modules", (done) -> 448 | 449 | vinylfs.src("#{dir}/fixtures/shim/*.js") 450 | .pipe(amdOptimize( 451 | "no_shim" 452 | wrapShim : true 453 | )) 454 | .on("data", (file) -> 455 | 456 | if file.relative == "no_shim.js" 457 | stringContents = file.contents.toString("utf8") 458 | ast = acorn.parse(stringContents) 459 | 460 | if ast.body[0].expression.type == "CallExpression" 461 | if ast.body[0].expression.callee.name == "define" 462 | assert.notEqual(ast.body[0].expression.arguments[0].value, "no_shim") 463 | ) 464 | .on("end", done) 465 | 466 | 467 | describe "nested dependencies", -> 468 | 469 | it "should not trace nested dependencies by default", (done) -> 470 | 471 | checkExpectedFiles( 472 | ["foo.js", "nested.js"] 473 | vinylfs.src("#{dir}/fixtures/core/*.js") 474 | .pipe(amdOptimize("nested")) 475 | done 476 | ) 477 | 478 | 479 | it "should trace nested dependencies", (done) -> 480 | 481 | checkExpectedFiles( 482 | ["bar.js", "foo.js", "nested.js"] 483 | 484 | vinylfs.src("#{dir}/fixtures/core/*.js") 485 | .pipe(amdOptimize( 486 | "nested" 487 | findNestedDependencies : true 488 | )) 489 | 490 | done 491 | ) 492 | 493 | it "should trace nested dependencies, with `requirejs`", (done) -> 494 | 495 | checkExpectedFiles( 496 | ["bar.js", "foo.js", "nested_requirejs.js"] 497 | 498 | vinylfs.src("#{dir}/fixtures/core/*.js") 499 | .pipe(amdOptimize( 500 | "nested_requirejs" 501 | findNestedDependencies : true 502 | )) 503 | 504 | done 505 | ) 506 | 507 | 508 | describe "config file", -> 509 | 510 | it "should read from config file from path", (done) -> 511 | 512 | checkExpectedFiles( 513 | ["index.js"] 514 | vinylfs.src("#{dir}/fixtures/config/index.js") 515 | .pipe(amdOptimize( 516 | "index" 517 | configFile : "#{dir}/fixtures/config/config.js" 518 | )) 519 | done 520 | ) 521 | 522 | 523 | it "should read from config file from vinyl stream", (done) -> 524 | 525 | checkExpectedFiles( 526 | ["index.js"] 527 | vinylfs.src("#{dir}/fixtures/config/index.js") 528 | .pipe(amdOptimize( 529 | "index" 530 | configFile : vinylfs.src("#{dir}/fixtures/config/config.js") 531 | )) 532 | done 533 | ) 534 | 535 | 536 | describe "special paths", -> 537 | 538 | it "should ignore requirejs plugins", (done) -> 539 | 540 | checkExpectedFiles( 541 | ["bar.js", "plugin.js"] 542 | vinylfs.src("#{dir}/fixtures/core/*.js") 543 | .pipe(amdOptimize("plugin")) 544 | done 545 | ) 546 | 547 | 548 | it "should ignore requirejs plugins (except text)", (done) -> 549 | 550 | checkExpectedFiles( 551 | ["bar.js", "text.html", "plugin-text.js"] 552 | vinylfs.src("#{dir}/fixtures/core/*.*") 553 | .pipe(amdOptimize("plugin-text")) 554 | .on("data", (file) -> 555 | if file.relative == "text.html" 556 | assert.equal( 557 | file.contents.toString(), 558 | fs.readFileSync("#{dir}/expected/text.html.js", "utf8").trim()) 559 | ) 560 | done 561 | ) 562 | 563 | it "should ignore requirejs plugins (except json)", (done) -> 564 | 565 | checkExpectedFiles( 566 | ["bar.js", "test.json", "plugin-json.js"] 567 | vinylfs.src("#{dir}/fixtures/core/*.*") 568 | .pipe(amdOptimize("plugin-json")) 569 | .on("data", (file) -> 570 | if file.relative == 'test.json' 571 | assert.equal( 572 | file.contents.toString(), 573 | fs.readFileSync("#{dir}/expected/test.json.js", "utf8").trim()) 574 | ) 575 | done 576 | ) 577 | 578 | it "should ignore empty paths", (done) -> 579 | 580 | checkExpectedFiles( 581 | ["index.js"] 582 | vinylfs.src("#{dir}/fixtures/core/index.js") 583 | .pipe(amdOptimize( 584 | "index" 585 | paths : { 586 | foo : "empty:" 587 | } 588 | )) 589 | done 590 | ) 591 | 592 | 593 | it "should apply prefix paths", (done) -> 594 | 595 | checkExpectedFiles( 596 | ["fuz/ahah.js", "duz.js"] 597 | 598 | vinylfs.src("#{dir}/fixtures/core/**/*.js") 599 | .pipe(amdOptimize( 600 | "duz" 601 | paths : { 602 | fuu : "fuz" 603 | } 604 | )) 605 | 606 | done 607 | ) 608 | 609 | 610 | it "should apply prefix paths #2", (done) -> 611 | checkExpectedFiles( 612 | ["path/to/module/foo.js", "index.js"] 613 | vinylfs.src("#{dir}/fixtures/shortcuts/**/*.js") 614 | .pipe(amdOptimize( 615 | "index" 616 | configFile : "#{dir}/fixtures/shortcuts/config.js" 617 | )) 618 | done 619 | ) 620 | 621 | 622 | it "should apply prefix paths with loader", (done) -> 623 | checkExpectedFiles( 624 | ["foo.js", "index.js"] 625 | amdOptimize.src( 626 | "index" 627 | baseUrl : "#{dir}/fixtures/shortcuts" 628 | configFile : "#{dir}/fixtures/shortcuts/config.js" 629 | ) 630 | done 631 | ) 632 | 633 | 634 | it "should ignore `exports` and `require` dependencies", (done) -> 635 | 636 | checkExpectedFiles( 637 | ["bar.js", "require_exports.js"] 638 | vinylfs.src("#{dir}/fixtures/core/*.js") 639 | .pipe(amdOptimize("require_exports")) 640 | done 641 | ) 642 | 643 | 644 | describe "errors", -> 645 | 646 | it "should throw a meaningful error when JS files have a SyntaxError", (done) -> 647 | 648 | amdOptimize.src( 649 | "return" 650 | loader : amdOptimize.loader( 651 | (name) -> "#{dir}/fixtures/errors/#{name}.js" 652 | ) 653 | ).on("error", (err) -> 654 | assert.ok(util.isError(err)) 655 | assert.ok(err.filename) 656 | assert.ok(err.pos) 657 | done() 658 | ) 659 | 660 | 661 | it "should passthrough the errors of loader streams", (done) -> 662 | 663 | amdOptimize.src( 664 | "foo" 665 | loader : amdOptimize.loader( 666 | (name) -> "#{dir}/fixtures/errors/#{name}.coffee" 667 | -> coffee() 668 | ) 669 | ).on("error", (err) -> 670 | assert.equal(err.name, "SyntaxError") 671 | done() 672 | ) 673 | 674 | 675 | it "should throw a syntax error, when parsing goes wrong", (done) -> 676 | 677 | amdOptimize.src( 678 | "foo" 679 | baseUrl : "test/fixtures/errors" 680 | ).on("error", (err) -> 681 | assert.ok(util.isError(err)) 682 | assert.equal("SyntaxError", err.name) 683 | done() 684 | ) 685 | 686 | 687 | it "should throw an error on plain circular dependencies", (done) -> 688 | 689 | amdOptimize.src( 690 | "cycle1" 691 | baseUrl : "test/fixtures/errors" 692 | ).on("error", (err) -> 693 | assert.ok(util.isError(err)) 694 | done() 695 | ) 696 | 697 | 698 | it "should throw an error on multiple anonymous define calls", (done) -> 699 | 700 | amdOptimize.src( 701 | "multiple_anonymous_defines" 702 | baseUrl : "test/fixtures/errors" 703 | ).on("error", (err) -> 704 | assert.ok(util.isError(err)) 705 | done() 706 | ) 707 | 708 | it "should work with circular dependencies when exports is used" 709 | # , (done) -> 710 | # # http://requirejs.org/docs/api.html#circular 711 | 712 | # checkExpectedFiles( 713 | # ["cycle_exports1.js", "cycle_exports2.js"] 714 | # amdOptimize.src( 715 | # "cycle_exports1" 716 | # baseUrl : "test/fixtures/errors" 717 | # ) 718 | # done 719 | # ) 720 | 721 | 722 | describe "commonjs", -> 723 | 724 | it "should parse modules in simplified commonjs notation", (done) -> 725 | 726 | checkExpectedFiles( 727 | ["bar.js", "foo.js"] 728 | vinylfs.src("#{dir}/fixtures/commonjs/*.js") 729 | .pipe(amdOptimize("foo")) 730 | done 731 | ) 732 | 733 | 734 | it "should parse modules in simplified commonjs notation (only `require` argument)", (done) -> 735 | 736 | checkExpectedFiles( 737 | ["bar.js", "fuz.js"] 738 | vinylfs.src("#{dir}/fixtures/commonjs/*.js") 739 | .pipe(amdOptimize("fuz")) 740 | done 741 | ) 742 | 743 | 744 | it "should parse modules in simplified commonjs notation (`require` as a statement)", (done) -> 745 | 746 | checkExpectedFiles( 747 | ["bar.js", "faz.js"] 748 | vinylfs.src("#{dir}/fixtures/commonjs/*.js") 749 | .pipe(amdOptimize("faz")) 750 | done 751 | ) 752 | 753 | 754 | it "should parse modules in simplified commonjs notation (`require` as a statement) #2", (done) -> 755 | 756 | checkAst( 757 | "foo.js" 758 | vinylfs.src("#{dir}/fixtures/commonjs/*.js") 759 | .pipe(amdOptimize("foo")) 760 | (ast) -> 761 | walk.simple(ast, CallExpression : (node) -> 762 | if node.callee.name == "define" 763 | assert.equal(node.arguments[1].elements[0].value, "require") 764 | assert.equal(node.arguments[1].elements[1].value, "exports") 765 | assert.equal(node.arguments[1].elements[2].value, "module") 766 | ) 767 | done 768 | ) 769 | 770 | 771 | describe "source maps", -> 772 | 773 | it "should create source maps", (done) -> 774 | 775 | vinylfs.src("#{dir}/fixtures/core/*.js") 776 | .pipe(sourcemaps.init()) 777 | .pipe(amdOptimize("index")) 778 | .on("data", (file) -> 779 | assert(file.sourceMap?) 780 | assert.equal(file.sourceMap.sources.toString(), file.relative) 781 | assert.deepEqual(file.sourceMap, require("./expected/#{file.relative}.map.json")) 782 | ) 783 | .on("end", done) 784 | 785 | 786 | it "should create source maps for files with comments", (done) -> 787 | 788 | vinylfs.src("#{dir}/fixtures/comments/*.js") 789 | .pipe(sourcemaps.init()) 790 | .pipe(amdOptimize("comment", { 791 | preserveComments : true 792 | })) 793 | .on("data", (file) -> 794 | assert(file.sourceMap?) 795 | assert.equal(file.sourceMap.sources.toString(), file.relative) 796 | assert.deepEqual(file.sourceMap, require("./expected/#{file.relative}.map.json")) 797 | # next line may fail if original contents have different indentation 798 | assert.equal( 799 | file.stringContents, 800 | fs.readFileSync(dir + "/fixtures/comments/#{file.relative}", "utf8") 801 | ) 802 | ) 803 | .on("end", done) 804 | 805 | 806 | it "should apply source maps to existing transformations", (done) -> 807 | 808 | vinylfs.src("#{dir}/fixtures/core/*.coffee") 809 | .pipe(sourcemaps.init()) 810 | .pipe(coffee()) 811 | .pipe(amdOptimize("index")) 812 | .on("data", (file) -> 813 | assert(file.sourceMap?) 814 | assert(_.contains(file.sourceMap.sources, file.relative)) 815 | assert(_.contains(file.sourceMap.sources, file.relative.replace(".js", ".coffee"))) 816 | ) 817 | # .pipe(sourcemaps.write(".")) 818 | # .pipe(vinylfs.dest("#{dir}/.tmp")) 819 | .on("end", done) 820 | 821 | 822 | it "should keep the relative paths", (done) -> 823 | 824 | checkExpectedFiles( 825 | ["fuz/ahah.js", "duu.js"] 826 | vinylfs.src("#{dir}/fixtures/core/**/*.js") 827 | .pipe(amdOptimize("duu")) 828 | .pipe(sourcemaps.init()) 829 | .on("data", (file) -> 830 | assert(file.sourceMap?) 831 | assert.equal(file.sourceMap.file, file.relative) 832 | assert(_.contains(file.sourceMap.sources, file.relative)) 833 | ) 834 | done 835 | ) 836 | 837 | it "should keep the relative paths #2", (done) -> 838 | 839 | checkExpectedFiles( 840 | ["concat.js.map", "concat.js"] 841 | vinylfs.src("#{dir}/fixtures/core/**/*.js") 842 | .pipe(amdOptimize("duu", { 843 | baseUrl: "#{dir}/fixtures/core" 844 | })) 845 | .pipe(sourcemaps.init()) 846 | .pipe(concat("concat.js")) 847 | .pipe(sourcemaps.write(".")) 848 | .on("data", (file) -> 849 | if path.extname(file.relative) == ".map" 850 | sourceMap = JSON.parse(file.contents) 851 | assert(sourceMap?) 852 | assert(_.contains(sourceMap.sources, "fuz/ahah.js")) 853 | assert(_.contains(sourceMap.sources, "duu.js")) 854 | ) 855 | done 856 | ) 857 | 858 | --------------------------------------------------------------------------------