├── .gitignore ├── test ├── deps │ ├── dep1.js │ └── umd.js ├── testError.js ├── utils │ └── configInputOutputDone.js ├── testUmd.js └── testBasic.js ├── package.json ├── index.js └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /test/deps/dep1.js: -------------------------------------------------------------------------------- 1 | define(function(){return "dependency1";}); -------------------------------------------------------------------------------- /test/deps/umd.js: -------------------------------------------------------------------------------- 1 | (function(){}(typeof define === 'function' && define['amd'] ? define : null)) -------------------------------------------------------------------------------- /test/testError.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var assert = require('assert'); 3 | var gutil = require('gulp-util'); 4 | var amdOptimize = require('../index'); 5 | 6 | describe("error testing", function(){ 7 | 8 | it('should emit a named module without dependencies unchanged', function(done){ 9 | 10 | var stream = amdOptimize({ 11 | baseUrl: __dirname+'/..' 12 | }); 13 | 14 | 15 | stream.on('error', function(error){ 16 | console.log(error); 17 | assert.ok(true); 18 | done(); 19 | }); 20 | 21 | stream.write(new gutil.File({ 22 | path: 'myModule.js', 23 | contents: new Buffer('define(["deps/not-existing"], function(){ return "test"; })') 24 | })); 25 | 26 | stream.end(); 27 | }); 28 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-amd-optimizer", 3 | "version": "0.6.0", 4 | "description": "Emits the entire dependency tree of one or more AMD modules, from leaves to root, and names anonymous modules", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/mariusGundersen/gulp-amd-optimizer" 12 | }, 13 | "keywords": [ 14 | "gulpplugin", 15 | "amd", 16 | "optimize", 17 | "require" 18 | ], 19 | "author": "Marius Gundersen", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/mariusGundersen/gulp-amd-optimizer/issues" 23 | }, 24 | "homepage": "https://github.com/mariusGundersen/gulp-amd-optimizer", 25 | "dependencies": { 26 | "gulp-util": "~3.0.6", 27 | "amd-optimizer": "~0.5.2", 28 | "through": "^2.3.8", 29 | "slash": "~1.0.0" 30 | }, 31 | "devDependencies": { 32 | "mocha": "~2.3.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/utils/configInputOutputDone.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var gutil = require('gulp-util'); 3 | var amdOptimize = require('../../index'); 4 | var path = require('path'); 5 | var fs = require('fs'); 6 | 7 | module.exports = function configInputOutputDone(options, input, output, done){ 8 | 9 | var stream = amdOptimize({ 10 | baseUrl: path.join(__dirname, '..') 11 | }, options); 12 | 13 | stream.on('data', function(file){ 14 | 15 | var expected = output.shift(); 16 | assert.equal(file.name, expected.path); 17 | assert.equal(file.contents.toString(), expected.contents); 18 | }); 19 | 20 | stream.on('end', done); 21 | 22 | stream.on('error', function(error){ 23 | done(error); 24 | }); 25 | 26 | input.forEach(function(file){ 27 | stream.write(new gutil.File({ 28 | path: path.join(__dirname, '../deps', file.path), 29 | base: path.join(__dirname, '../deps'), 30 | contents: new Buffer(file.contents) 31 | })); 32 | }); 33 | 34 | stream.end(); 35 | }; -------------------------------------------------------------------------------- /test/testUmd.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var configInputOutputDone = require('./utils/configInputOutputDone'); 3 | 4 | describe("umd testing", function(){ 5 | 6 | 7 | 8 | 9 | it('should not name umd modules when umd is false', function(done){ 10 | 11 | configInputOutputDone({ 12 | umd: false 13 | }, [ 14 | { 15 | path: 'myModule.js', 16 | contents: 'define(["deps/umd"], function(){ return "test"; })' 17 | } 18 | ], [ 19 | { 20 | path: 'deps/umd', 21 | contents: "(function(){}(typeof define === 'function' && define['amd'] ? define : null))\n\n" 22 | }, 23 | { 24 | path: 'myModule', 25 | contents: 'define("myModule", ["deps/umd"], function(){ return "test"; })\n\n' 26 | } 27 | ], done); 28 | }); 29 | 30 | 31 | 32 | it('should name umd modules when umd is false', function(done){ 33 | 34 | configInputOutputDone({ 35 | umd: true 36 | }, [ 37 | { 38 | path: 'myModule.js', 39 | contents: 'define(["deps/umd"], function(){ return "test"; })' 40 | } 41 | ], [ 42 | { 43 | path: 'deps/umd', 44 | contents: "(function(){}(typeof define === 'function' && define['amd'] ? define.bind(null, \"deps/umd\") : null))\n\n" 45 | }, 46 | { 47 | path: 'myModule', 48 | contents: 'define("myModule", ["deps/umd"], function(){ return "test"; })\n\n' 49 | } 50 | ], done); 51 | }); 52 | }); -------------------------------------------------------------------------------- /test/testBasic.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var configInputOutputDone = require('./utils/configInputOutputDone'); 3 | 4 | describe("basic testing", function(){ 5 | 6 | it('should emit a named module without dependencies unchanged', function(done){ 7 | 8 | configInputOutputDone({ 9 | }, [ 10 | { 11 | path: 'myModule.js', 12 | contents: 'define("myModule", function(){ return "test"; })' 13 | } 14 | ], [ 15 | { 16 | path: 'myModule', 17 | contents: 'define("myModule", function(){ return "test"; })\n\n' 18 | } 19 | ], done); 20 | }); 21 | 22 | 23 | it('should emit a named anonymous modules', function(done){ 24 | 25 | configInputOutputDone({ 26 | }, [ 27 | { 28 | path: 'myModule.js', 29 | contents: 'define(function(){ return "test"; })' 30 | } 31 | ], [ 32 | { 33 | path: 'myModule', 34 | contents: 'define("myModule", function(){ return "test"; })\n\n' 35 | } 36 | ], done); 37 | }); 38 | 39 | it('should emit a dependency', function(done){ 40 | 41 | configInputOutputDone({ 42 | }, [ 43 | { 44 | path: 'myModule.js', 45 | contents: 'define(["deps/dep1"], function(){ return "test"; })' 46 | } 47 | ], [ 48 | { 49 | path: 'deps/dep1', 50 | contents: 'define("deps/dep1", function(){return "dependency1";});\n\n' 51 | }, 52 | { 53 | path: 'myModule', 54 | contents: 'define("myModule", ["deps/dep1"], function(){ return "test"; })\n\n' 55 | } 56 | ], done); 57 | }); 58 | 59 | it('should emit one file per module', function(done){ 60 | 61 | configInputOutputDone({ 62 | }, [ 63 | { 64 | path: 'myModule.js', 65 | contents: 'define("dep1", function(){return "dependency1";}); define(["dep1"], function(){ return "test"; });' 66 | } 67 | ], [ 68 | { 69 | path: 'dep1', 70 | contents: 'define("dep1", function(){return "dependency1";});\n\n' 71 | }, 72 | { 73 | path: 'myModule', 74 | contents: 'define("myModule", ["dep1"], function(){ return "test"; });\n\n' 75 | } 76 | ], done); 77 | }); 78 | }); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var optimize = require('amd-optimizer'); 3 | var gutil = require('gulp-util'); 4 | var through = require('through'); 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | var slash = require('slash'); 8 | 9 | var File = gutil.File; 10 | var Buffer = require('buffer').Buffer; 11 | var PluginError = gutil.PluginError; 12 | var baseName = /^(.*?)\.\w+$/; 13 | 14 | function loadFile(path, name, baseUrl, done){ 15 | fs.readFile(path, function(err, contents){ 16 | if(err) return done(err); 17 | var file = new File({ 18 | path: path, 19 | base: baseUrl, 20 | contents: contents 21 | }); 22 | file.name = name; 23 | done(null, file); 24 | }) 25 | } 26 | 27 | module.exports = function (config, options) { 28 | 29 | if(!config || typeof config !== 'object'){ 30 | throw new PluginError('gulp-amd-optimize', 'first argument, the config, must be an object'); 31 | }else if('baseUrl' in config === false){ 32 | throw new PluginError('gulp-amd-optimize', 'baseUrl is required in the config'); 33 | } 34 | 35 | options = options || {}; 36 | 37 | var sourceMapSupport = false; 38 | var cwd; 39 | 40 | var optimizer = optimize(config, options); 41 | 42 | optimizer.on('dependency', function(dependency){ 43 | loadFile(dependency.path, dependency.name, config.baseUrl, function(err, file){ 44 | if(err){ 45 | optimizer.error('Could not load `'+dependency.name+'`\n required by `'+dependency.requiredBy+'`\n from path `'+dependency.path+'`\n because of '+err); 46 | }else{ 47 | optimizer.addFile(file); 48 | } 49 | }); 50 | }); 51 | 52 | function onData(file) { 53 | if (file.isNull()) { 54 | this.push(file); 55 | } 56 | 57 | if(file.sourceMap){ 58 | sourceMapSupport = true; 59 | } 60 | 61 | if (file.isStream()) { 62 | this.emit('error', new PluginError('gulp-amd-optimize', 'Streaming not supported')); 63 | return 64 | } 65 | 66 | cwd = cwd || file.cwd; 67 | file.name = baseName.exec(file.relative)[1]; 68 | 69 | optimizer.addFile(file); 70 | 71 | } 72 | 73 | function onEnd(){ 74 | optimizer.done(function(output){ 75 | output.forEach(function(module){ 76 | var file = new File({ 77 | path: path.join(config.baseUrl, module.file.relative), 78 | base: config.baseUrl, 79 | cwd: cwd, 80 | contents: new Buffer(module.content + '\n\n') 81 | }); 82 | 83 | file.name = module.name; 84 | 85 | if(sourceMapSupport){ 86 | module.map.sourcesContent = [module.source]; 87 | 88 | file.sourceMap = module.map; 89 | } 90 | this.queue(file); 91 | }.bind(this)); 92 | this.queue(null); 93 | }.bind(this)); 94 | } 95 | 96 | var transformer = through(onData, onEnd); 97 | 98 | optimizer.on('error', function(error){ 99 | transformer.emit('error', error); 100 | }); 101 | 102 | return transformer; 103 | }; -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # [gulp](https://github.com/wearefractal/gulp)-amd-optimizer 2 | 3 | > Emits the entire dependency tree of one or more AMD modules, from leaves to root, and names anonymous modules 4 | 5 | This module does not combine the modules into a single file. It is up to you to decide how to concat/minify the modules, using for example uglify, closure-compiler or just concatenating it together. 6 | 7 | ## Install 8 | 9 | ```bash 10 | $ npm install --save-dev gulp-amd-optimizer 11 | ``` 12 | 13 | 14 | ## Usage 15 | 16 | ```js 17 | var gulp = require('gulp'); 18 | var amdOptimize = require('gulp-amd-optimizer'); 19 | var concat = require('gulp-concat'); 20 | 21 | var requireConfig = { 22 | baseUrl: __dirname 23 | }; 24 | var options = { 25 | umd: false 26 | }; 27 | 28 | gulp.task('default', function () { 29 | return gulp.src('src/*.js', {base: requireConfig.baseUrl}) 30 | .pipe(amdOptimize(requireConfig, options)) 31 | .pipe(concat('modules.js')) 32 | .pipe(gulp.dest('dist')); 33 | }); 34 | ``` 35 | 36 | ### Output 37 | 38 | gulp-amd-optimizer accepts JS files containing one or more AMD modules. Anonymous modules are given the name of the file (including the path, relative to the baseUrl, without the `.js` extension). Dependencies of modules are found and a full dependency tree is constructed. Finally the tree is sorted in topological order (from leaves to root nodes) and each module is emitted as a single file. 39 | 40 | This plugin does not attempt to concat the files. This is the job of other plugins. 41 | 42 | 43 | ### Config 44 | 45 | The amdOptimizer method takes the RequireJS configuration as its first argument, as described in the [RequireJS documentation](http://requirejs.org/docs/api.html#config), with some slight differences: 46 | 47 | * `baseUrl`: *string* The path from which modules are loaded. [RequireJS documentation](http://requirejs.org/docs/api.html#config-baseUrl) 48 | * `exclude`: *[string]* List of modules and folders NOT to load. 49 | 50 | ### Options 51 | 52 | The second argument to amdOptimizer is optional and can be used to change the way modules are found and named. It consists of the following options: 53 | 54 | * `umd`: *boolean* When true, try to find umd modules and name them. See https://github.com/umdjs/umd 55 | 56 | ### Sourcemap 57 | 58 | gulp-amd-optimzer supports the [gulp-sourcemaps](https://github.com/floridoo/gulp-sourcemaps/) plugin. The example below shows how sourcemaps can be used. 59 | 60 | ```js 61 | var gulp = require('gulp'); 62 | var amdOptimize = require('gulp-amd-optimizer'); 63 | var concat = require('gulp-concat'); 64 | var sourcemap = require('gulp-sourcemaps'); 65 | 66 | gulp.task('default', function () { 67 | return gulp.src('src/*.js', { base: amdConfig.baseUrl }) 68 | .pipe(sourcemap.init()) 69 | .pipe(amdOptimize(amdConfig)) 70 | .pipe(concat('modules.js')) 71 | .pipe(sourcemap.write('./', { includeContent: false, sourceRoot: '../src' })) 72 | .pipe(gulp.dest('dist')); 73 | }); 74 | 75 | var amdConfig = { 76 | baseUrl: 'src', 77 | path:{ 78 | 'lib': '../lib' 79 | } 80 | exclude: [ 81 | 'jQuery' 82 | ] 83 | }; 84 | ``` 85 | 86 | Sourcemaps can be difficult to get right, so it is a good idea to follow these rules: 87 | 88 | * The `base` option passed to `gulp.src` should be the same as `baseUrl` in the `amdConfig`. 89 | * The sourcemap should be written to the same folder (`./`) as the output. 90 | * The sourceRoot should be the relative path from the destination folder (the argument to `gulp.dest`) to the `base`. 91 | 92 | ## License 93 | 94 | [MIT](http://opensource.org/licenses/MIT) © [Marius Gundersen](http://mariusgundersen.net) 95 | --------------------------------------------------------------------------------