├── .editorconfig ├── .gitignore ├── .jscsrc ├── .jshintrc ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin └── lodash-modularize ├── defaults.json ├── gulpfile.js ├── package.json ├── src ├── Error.js ├── cli-build.js ├── esperanto-build.js ├── fs.js ├── lodash-chainable.js ├── lodash-modularize.js ├── lodashModules.js ├── parseForModules.js └── updateReferences.js ├── templates ├── chain-build.tpl └── import-build.tpl └── test ├── .jshintrc ├── samples ├── amd.js ├── chaining.js ├── cjs.js └── es6.js ├── setup ├── node.js └── setup.js ├── tmp └── es6.js └── unit └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | EditorConfig is awesome: http://EditorConfig.org 2 | 3 | root = true; 4 | 5 | [*] 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | [*.js] 10 | indent_style = space 11 | indent_size = 2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | test/out/ 3 | test/tmp/ 4 | 5 | #####=== Node ===##### 6 | 7 | # Logs 8 | logs 9 | *.log 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 23 | .grunt 24 | 25 | # node-waf configuration 26 | .lock-wscript 27 | 28 | # Compiled binary addons (http://nodejs.org/api/addons.html) 29 | build/Release 30 | 31 | # Dependency directory 32 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 33 | node_modules 34 | 35 | # Debug log from npm 36 | npm-debug.log 37 | 38 | #####=== Linux ===##### 39 | *~ 40 | 41 | # KDE directory preferences 42 | .directory 43 | 44 | # Linux trash folder which might appear on any partition or disk 45 | .Trash-* 46 | 47 | #####=== Windows ===##### 48 | # Windows image file caches 49 | Thumbs.db 50 | ehthumbs.db 51 | 52 | # Folder config file 53 | Desktop.ini 54 | 55 | # Recycle Bin used on file shares 56 | $RECYCLE.BIN/ 57 | 58 | # Windows Installer files 59 | *.cab 60 | *.msi 61 | *.msm 62 | *.msp 63 | 64 | # Windows shortcuts 65 | *.lnk 66 | 67 | #####=== OSX ===##### 68 | .DS_Store 69 | .AppleDouble 70 | .LSOverride 71 | 72 | # Icon must end with two \r 73 | Icon 74 | 75 | 76 | # Thumbnails 77 | ._* 78 | 79 | # Files that might appear on external disk 80 | .Spotlight-V100 81 | .Trashes 82 | 83 | # Directories potentially created on remote AFP share 84 | .AppleDB 85 | .AppleDesktop 86 | Network Trash Folder 87 | Temporary Items 88 | .apdisk 89 | 90 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "google", 3 | "esnext": true, 4 | "maximumLineLength": null 5 | } -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise" : true, 3 | "camelcase" : true, 4 | "eqeqeq" : true, 5 | "forin" : false, 6 | "immed" : true, 7 | "indent" : 2, 8 | "latedef" : true, 9 | "newcap" : true, 10 | "noarg" : true, 11 | "nonbsp" : true, 12 | "nonew" : true, 13 | "plusplus" : false, 14 | "undef" : true, 15 | "unused" : true, 16 | "strict" : false, 17 | "maxparams" : 6, 18 | "maxdepth" : 3, 19 | "maxstatements" : 20, 20 | "maxcomplexity" : 15, 21 | "maxlen" : 100, 22 | 23 | "asi" : false, 24 | "boss" : true, 25 | "debug" : true, 26 | "eqnull" : true, 27 | "esnext" : true, 28 | "evil" : false, 29 | "expr" : false, 30 | "funcscope" : false, 31 | "globalstrict" : false, 32 | "iterator" : false, 33 | "lastsemic" : false, 34 | "loopfunc" : false, 35 | "maxerr" : 50, 36 | "notypeof" : false, 37 | "proto" : false, 38 | "scripturl" : false, 39 | "shadow" : false, 40 | "supernew" : false, 41 | "validthis" : false, 42 | "noyield" : false, 43 | 44 | "browser" : true, 45 | "couch" : false, 46 | "devel" : false, 47 | "dojo" : false, 48 | "jquery" : false, 49 | "mootools" : false, 50 | "node" : true, 51 | "nonstandard" : false, 52 | "prototypejs" : false, 53 | "rhino" : false, 54 | "worker" : false, 55 | "wsh" : false, 56 | "yui" : false 57 | } 58 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | 3 | #####=== Node ===##### 4 | 5 | # Logs 6 | logs 7 | *.log 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directory 30 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 31 | node_modules 32 | 33 | # Debug log from npm 34 | npm-debug.log 35 | 36 | #####=== Linux ===##### 37 | *~ 38 | 39 | # KDE directory preferences 40 | .directory 41 | 42 | # Linux trash folder which might appear on any partition or disk 43 | .Trash-* 44 | 45 | #####=== OSX ===##### 46 | .DS_Store 47 | .AppleDouble 48 | .LSOverride 49 | 50 | # Icon must end with two \r 51 | Icon 52 | 53 | 54 | # Thumbnails 55 | ._* 56 | 57 | # Files that might appear on external disk 58 | .Spotlight-V100 59 | .Trashes 60 | 61 | # Directories potentially created on remote AFP share 62 | .AppleDB 63 | .AppleDesktop 64 | Network Trash Folder 65 | Temporary Items 66 | .apdisk 67 | 68 | #####=== Windows ===##### 69 | # Windows image file caches 70 | Thumbs.db 71 | ehthumbs.db 72 | 73 | # Folder config file 74 | Desktop.ini 75 | 76 | # Recycle Bin used on file shares 77 | $RECYCLE.BIN/ 78 | 79 | # Windows Installer files 80 | *.cab 81 | *.msi 82 | *.msm 83 | *.msp 84 | 85 | # Windows shortcuts 86 | *.lnk 87 | 88 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.12" 5 | - "io.js" 6 | sudo: false 7 | script: "gulp coverage" 8 | after_success: 9 | - npm install -g codeclimate-test-reporter 10 | - codeclimate < coverage/lcov.info 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Graeme Yeates 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lodash-modularize [![Dependency Status](https://david-dm.org/megawac/lodash-modularize.svg)](https://david-dm.org/megawac/lodash-modularize) 2 | 3 | Lodash is starting to get pretty heafty; this is a tool to generate modular lodash builds so lodash only includes what you use. This can lead to faster startup and smaller builds (when using `compile`, `browserify`, `r.js`, etc). 4 | 5 | If you're using `babel` checkout [`babel-plugin-lodash`](https://github.com/megawac/babel-plugin-lodash) for a different approach. 6 | 7 | ## Whats in the Box 8 | 9 | IMAGE ALT TEXT HERE 12 | 13 | ### Features 14 | 15 | - Detect lodash methods/modules being used in source code 16 | - Compile a perfect [custom lodash build using the cli](https://lodash.com/custom-builds) 17 | - Compile a custom modular `lodash.js` which imports the exact modules you use 18 | - Update references (e.g.) `require('lodash')` to `require('./src/custom-lodash'` 19 | - Supports AMD, CJS, ES6 and UMD 20 | - Natural recompilation: if you decide to output a build (updating references), the tool recognizes `lodash` features coming from the output path (e.g. `lib/lodash.js`) in addition to regular lodash sources. 21 | - Supports using [lodash npm modules](https://www.npmjs.com/browse/keyword/lodash-modularized) 22 | - Other sweetness (see below and try `lodash-modularize --help`) 23 | 24 | ### Example Usage 25 | 26 | All examples are taken from this project 27 | 28 | ```sh 29 | # List all the method's being used in src 30 | lodash-modularize src/** --list 31 | # => assign,chain,flatten,includes,isArray,reject,result,template,uniq,zipObject 32 | 33 | lodash-modularize src/** 34 | # 35 | 36 | lodash-modularize src/** -o src/depends/lodash.js 37 | 38 | lodash-modularize src/** -o src/depends/lodash.js --format es6 39 | 40 | # Set the global variable to search for to `lodash` 41 | lodash-modularize src/** -o src/depends/lodash.js --global lodash --exports umd 42 | 43 | # Compile the code using lodash-cli! 44 | lodash-modularize src/** -o src/depends/lodash.js --amd --compile 45 | 46 | # Update the projects using lodash to use the built depends/lodash instead 47 | lodash-modularize src/** -o depends/lodash.js --update 48 | ``` 49 | 50 | ##### Fancy `package.json` script 51 | 52 | Use this tool to easily use [lodash npm modules](https://www.npmjs.com/browse/keyword/lodash-modularized) and avoid installing the entire lodash package (set `lodash` as a dev dep)! 53 | 54 | ```js 55 | { 56 | "devDependencies": {"lodash": "^3.0", "lodash-modularize": "^1.0"}, 57 | "scripts": { 58 | "prepublish": "lodash-modularize src/**.js -o src/depends/lodash.js -u --use-npm-modules --install-npm-modles" 59 | } 60 | } 61 | ``` 62 | 63 | ### So what can it detect 64 | 65 | **app.js** 66 | ```js 67 | import _, {sortBy, uniq} from 'lodash'; 68 | let log = require('logger'), 69 | lodash = require('lodash'); 70 | 71 | let result = sortBy(_.flatten(uniq([{a: 1}, {a: 2}, {a: 1}, {a: 0}])), 'a'); 72 | lodash.each(result, log); 73 | ``` 74 | 75 | ```sh 76 | $ lodash-modularize app.js --list 77 | # => each, flatten, sortBy, uniq 78 | 79 | $ lodash-modularize ./test/sample.js --cjs -o lodash.js 80 | ``` 81 | **lodash.js** 82 | ```js 83 | var each = require('lodash/collection/each'); 84 | var flatten = require('lodash/array/flatten'); 85 | var sortBy = require('lodash/collection/sortBy'); 86 | var uniq = require('lodash/array/uniq'); 87 | 88 | function lodash() { 89 | throw 'lodash chaining is not included in this build... Try rebuilding.'; 90 | } 91 | module.exports = lodash; 92 | 93 | lodash.each = each; 94 | lodash.flatten = flatten; 95 | lodash.sortBy = sortBy; 96 | lodash.uniq = uniq; 97 | ``` 98 | 99 | And many other patterns including globals (opt-in), **chaining**, and mixins. 100 | 101 | ## Notes 102 | 103 | Lazy chaining is not fully supported (it works but its not lazy). 104 | 105 | You should use in conjunction with linters (jshint/eslint/etc) as this won't detect unused variables. 106 | 107 | All though we go out of our way to be robust and support various ways to detect lodash imports of lodash there are things we don't bother to handle. For example if you do any of these things, we'll probably miss it (same goes for global variables) 108 | 109 | ```js 110 | const _ = require('lodash'); 111 | const lodash = _; 112 | 113 | function reassignment() { 114 | let _ = 'reassigned'.trim(); 115 | return _; 116 | } 117 | ``` 118 | -------------------------------------------------------------------------------- /bin/lodash-modularize: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var defaults = require('../defaults.json'); 4 | var screenWidth = require('window-size').width; 5 | 6 | var argv = require('yargs') 7 | .usage('Usage: $0 [options]') 8 | .example('$0 src/**.js --format amd -o src/lodash.js --lodash-path bower/lodash', 'Generate a UMD build for source files') 9 | .example('$0 src/**.js -g _ --exports es6', 'Generate a UMD build for source files') 10 | .example('$0 src/**.js --list', 'List the used lodash functions') 11 | .demand(1) 12 | 13 | .option('output', { 14 | default: defaults.output, 15 | alias: 'o', 16 | describe: 'file to generate' 17 | }) 18 | .nargs('output', 1) 19 | 20 | .option('update', { 21 | alias: 'u', 22 | type: 'boolean', 23 | describe: 'Update the files using lodash to use the built file' 24 | }) 25 | .implies('update', 'output') 26 | 27 | .option('production', { 28 | type: 'boolean', 29 | describe: 'Generate an optimized & minified build' 30 | }) 31 | .implies('production', 'output') 32 | .implies('production', 'compile') 33 | 34 | .option('format', { 35 | default: defaults.format, 36 | alias: 'f', 37 | describe: 'modular format to export (es6,amd,cjs)' 38 | }) 39 | .nargs('format', 1) 40 | 41 | .option('exports', { 42 | default: defaults.exports, 43 | alias: 'export', 44 | describe: 'module format to export (umd,es6,cjs,amd)' 45 | }) 46 | .option('es6', { 47 | describe: 'Set to es6 export', 48 | type: 'boolean' 49 | }) 50 | .option('amd', { 51 | describe: 'Set to amd export', 52 | type: 'boolean' 53 | }) 54 | .option('cjs', { 55 | describe: 'Set to cjs export', 56 | type: 'boolean' 57 | }) 58 | 59 | .option('lodash', { 60 | default: defaults.lodash, 61 | describe: 'The path to search and use for lodash (e.g. lodash-compat)' 62 | }) 63 | 64 | .option('lodash-path', { 65 | default: defaults.lodashPath, 66 | describe: 'Lodash base path (useful for AMD)' 67 | }) 68 | 69 | .option('use-npm-modules', { 70 | describe: 'Whether to use (smaller) npm lodash modules (see docs)', 71 | type: 'boolean', 72 | alias: ['n'] 73 | }) 74 | .implies('use-npm-modules', 'output') 75 | 76 | .option('install-npm-modules', { 77 | type: 'boolean' 78 | }) 79 | 80 | .option('global', { 81 | default: defaults.global, 82 | alias: 'g', 83 | describe: 'Global lodash variable (e.g. _ or lodash)' 84 | }) 85 | .nargs('g', 1) 86 | 87 | .option('compile', { 88 | alias: ['c'], 89 | describe: 'Whether to compile the results like lodash cli', 90 | type: 'boolean' 91 | }) 92 | 93 | .boolean('list') 94 | .describe('list', 'List the use lodash modules instead of building a file') 95 | 96 | .version(function() { 97 | return require('../package').version; 98 | }) 99 | .wrap(Math.min(screenWidth, 120)) 100 | .argv; 101 | 102 | // I want it all 103 | require('bluebird').longStackTraces(); 104 | var modularize = require('..'); 105 | var inquirer = require('inquirer'); 106 | var _ = require('lodash'); 107 | var exec = require('child_process').exec; 108 | 109 | // Work around yargs not doing it for us 110 | if (typeof argv.f === 'string') { 111 | argv.format = argv.f = argv.f.split(','); 112 | } 113 | 114 | if (!argv.lodashPath) { 115 | argv.lodashPath = typeof argv.lodash === 'string' ? 116 | argv.lodash : 117 | argv.lodash[0]; 118 | } 119 | 120 | if (argv.es6) { 121 | argv.exports = 'es6'; 122 | } else if (argv.amd) { 123 | argv.exports = 'amd'; 124 | } else if (argv.cjs) { 125 | argv.exports = 'cjs'; 126 | } 127 | argv.export = argv.exports; 128 | 129 | argv.useNpmModules = argv.useNpmModules || argv.installNpmModules; 130 | 131 | modularize(argv._, argv) 132 | .then(function(result) { 133 | if (argv.list) { 134 | console.log(result.join(',').bold); 135 | } else if (!argv.output) { 136 | console.log(result.code); 137 | } else if (argv.useNpmModules) { 138 | function save() { 139 | var mappings = require('lodash-cli/lib/mapping'); 140 | var methods = result.methods.map(function(method) { 141 | method = _.result(mappings.aliasToReal, method, method); 142 | return 'lodash.' + method.toLowerCase(); 143 | }); 144 | 145 | var cmd = exec('npm install --save ' + methods.join(' ')); 146 | cmd.stdout.on('data', console.log); 147 | cmd.stderr.on('data', console.error); 148 | } 149 | 150 | if (argv.installNpmModules) { 151 | return save(); 152 | } 153 | inquirer.prompt([{ 154 | type: "confirm", 155 | message: "Install the appropriate npm modules now?", 156 | name: "install", 157 | default: false 158 | }], function(opts) { 159 | if (opts.install) { 160 | save(); 161 | } 162 | }); 163 | } 164 | }) 165 | .catch(function(error) { 166 | // process.exit(1) after the error is outputted 167 | setTimeout(process.exit, 100, 1); 168 | throw error; 169 | }); 170 | -------------------------------------------------------------------------------- /defaults.json: -------------------------------------------------------------------------------- 1 | { 2 | "compile": false, 3 | "exports": "umd", 4 | "format": ["cjs", "es6", "amd"], 5 | "global": null, 6 | "list": false, 7 | "lodash": ["lodash", "lodash-compact", "lodash-fp"], 8 | "lodashPath": null, 9 | "output": null, 10 | "production": false, 11 | "update": false 12 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const $ = require('gulp-load-plugins')(); 3 | const del = require('del'); 4 | const path = require('path'); 5 | const mkdirp = require('mkdirp'); 6 | const isparta = require('isparta'); 7 | 8 | const manifest = require('./package.json'); 9 | const config = manifest.nodeBoilerplateOptions; 10 | const mainFile = manifest.main; 11 | const destinationFolder = path.dirname(mainFile); 12 | 13 | // Remove the built files 14 | gulp.task('clean', function(cb) { 15 | del([destinationFolder], cb); 16 | }); 17 | 18 | // Send a notification when JSHint fails, 19 | // so that you know your changes didn't build 20 | function jshintNotify(file) { 21 | if (!file.jshint) { return; } 22 | return file.jshint.success ? false : 'JSHint failed'; 23 | } 24 | 25 | function jscsNotify(file) { 26 | if (!file.jscs) { return; } 27 | return file.jscs.success ? false : 'JSCS failed'; 28 | } 29 | 30 | function createLintTask(taskName, files) { 31 | gulp.task(taskName, function() { 32 | return gulp.src(files) 33 | .pipe($.plumber()) 34 | .pipe($.jshint()) 35 | .pipe($.jshint.reporter('jshint-stylish')) 36 | .pipe($.notify(jshintNotify)) 37 | .pipe($.jscs()) 38 | .pipe($.notify(jscsNotify)) 39 | .pipe($.jshint.reporter('fail')); 40 | }); 41 | } 42 | 43 | // Lint our source code 44 | createLintTask('lint-src', ['src/**/*.js']) 45 | 46 | // Lint our test code 47 | createLintTask('lint-test', ['test/unit/*.js']) 48 | 49 | // Build two versions of the library 50 | gulp.task('build', ['lint-src', 'clean'], function() { 51 | 52 | // Create our output directory 53 | mkdirp.sync(destinationFolder); 54 | return gulp.src('src/**/*.js') 55 | .pipe($.plumber()) 56 | .pipe($.babel({ blacklist: ['useStrict'] })) 57 | .pipe(gulp.dest(destinationFolder)); 58 | }); 59 | 60 | function test() { 61 | return gulp.src(['test/setup/node.js', 'test/unit/**/*.js'], {read: false}) 62 | .pipe($.plumber()) 63 | .pipe($.mocha({reporter: 'dot', globals: config.mochaGlobals})); 64 | } 65 | 66 | // Make babel preprocess the scripts the user tries to import from here on. 67 | require('babel/register'); 68 | 69 | gulp.task('coverage', function(done) { 70 | gulp.src(['src/*.js']) 71 | .pipe($.plumber()) 72 | .pipe($.istanbul({ instrumenter: isparta.Instrumenter })) 73 | .pipe($.istanbul.hookRequire()) 74 | .on('finish', function() { 75 | return test() 76 | .pipe($.istanbul.writeReports()) 77 | .on('end', done); 78 | }); 79 | }); 80 | 81 | 82 | // Lint and run our tests 83 | gulp.task('test', ['lint-src', 'lint-test'], test); 84 | 85 | // Run the headless unit tests as you make changes. 86 | gulp.task('watch', ['test'], function() { 87 | gulp.watch(['src/**/*', 'test/**/*', 'package.json', '**/.jshintrc', '.jscsrc'], ['test']); 88 | }); 89 | 90 | // An alias of test 91 | gulp.task('default', ['test']); 92 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lodash-modularize", 3 | "version": "1.3.3", 4 | "description": "Generate modular lodash builds for exactly what you use", 5 | "main": "dist/lodash-modularize.js", 6 | "scripts": { 7 | "test": "gulp", 8 | "build": "gulp build", 9 | "coverage": "gulp coverage", 10 | "prepublish": "npm run build" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/megawac/lodash-modularize.git" 15 | }, 16 | "keywords": [], 17 | "author": "Graeme Yeates ", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/megawac/lodash-modularize/issues" 21 | }, 22 | "homepage": "https://github.com/megawac/lodash-modularize", 23 | "devDependencies": { 24 | "babel": "^5.0.0", 25 | "chai": "^2.0.0", 26 | "del": "^1.1.1", 27 | "gulp": "^3.8.10", 28 | "gulp-babel": "^5.0.0", 29 | "gulp-istanbul": "^0.6.0", 30 | "gulp-jscs": "^1.4.0", 31 | "gulp-jshint": "^1.9.0", 32 | "gulp-load-plugins": "^0.8.0", 33 | "gulp-mocha": "^2.0.0", 34 | "gulp-notify": "^2.1.0", 35 | "gulp-plumber": "^0.6.6", 36 | "isparta": "^2.0.0", 37 | "jshint-stylish": "^1.0.0", 38 | "mkdirp": "^0.5.0", 39 | "mocha": "^2.1.0", 40 | "sinon": "^1.12.2", 41 | "sinon-chai": "^2.7.0" 42 | }, 43 | "nodeBoilerplateOptions": { 44 | "mochaGlobals": [ 45 | "stub", 46 | "spy", 47 | "expect" 48 | ] 49 | }, 50 | "preferGlobal": true, 51 | "dependencies": { 52 | "acorn": "^1.0.0", 53 | "acorn-umd": "^0.4.0", 54 | "bluebird": "^2.9.14", 55 | "colors": "^1.0.3", 56 | "esperanto": "^0.6.25", 57 | "estraverse": "^4.1.0", 58 | "glob": "^5.0.3", 59 | "inquirer": "^0.8.2", 60 | "lodash": "^3.7.0", 61 | "lodash-cli": "^3.7.0", 62 | "recast": "^0.10.11", 63 | "window-size": "^0.1.0", 64 | "yargs": "^3.7.0" 65 | }, 66 | "bin": "bin/lodash-modularize" 67 | } 68 | -------------------------------------------------------------------------------- /src/Error.js: -------------------------------------------------------------------------------- 1 | import 'colors'; 2 | const pkg = require('../package.json'); 3 | 4 | export default class ModularizeError extends Error { 5 | constructor(message, file) { 6 | super(); 7 | Object.defineProperty(this, 'message', { 8 | value: `${message} in ${file.underline}`.red.bold + ` 9 | If this is an issue please report to ${pkg.bugs.url} 10 | ` 11 | }); 12 | } 13 | 14 | get name () { 15 | return this.constructor.name; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/cli-build.js: -------------------------------------------------------------------------------- 1 | import cli from 'lodash-cli'; 2 | import Promise from 'bluebird'; 3 | 4 | // Valid exports are “amd”, “commonjs”, “es”, “global”, “iojs”, “node”, “npm”, & “none”. 5 | const exportMap = { 6 | umd: 'amd,commonjs,global,iojs', 7 | amd: 'amd', 8 | cjs: 'commonjs' 9 | }; 10 | 11 | export default function build(methods, options) { 12 | let opts = [ 13 | // Detect the build type 14 | /compat/i.test(options.lodashPath) ? 'compat' : 'modern', 15 | // Requested the required methods 16 | `include=${methods.join(',')}`, 17 | options.production ? '--production' : '--development', 18 | // Map lodash-cli's export formats to ours 19 | `exports=${exportMap[options.exports]}`, 20 | '--silent' 21 | ]; 22 | 23 | return new Promise(resolve => { 24 | cli(opts, out => resolve(out.source)); 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /src/esperanto-build.js: -------------------------------------------------------------------------------- 1 | import esperanto from 'esperanto'; 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | import lodash, {first, includes, result, template} from 'lodash'; 5 | 6 | import {aliasToReal} from 'lodash-cli/lib/mapping'; 7 | 8 | import chainableMethods from './lodash-chainable'; 9 | 10 | const buildPath = path.join(__dirname, '../templates/import-build.tpl'); 11 | const chainPath = path.join(__dirname, '../templates/chain-build.tpl'); 12 | const normalTemplate = template(fs.readFileSync(buildPath)); 13 | const chainTemplate = template(fs.readFileSync(chainPath)); 14 | 15 | export default function build(methods, modules, options) { 16 | let chainBuild = includes(methods, 'chain'); 17 | 18 | let _path = result(options, 'lodashPath'); 19 | if (_path) { 20 | let {ext, dir, name, base} = path.parse(_path); 21 | // Don't rel a cjs import 22 | if (options.output != null && 23 | (ext !== '' || dir !== '' || name !== base) 24 | ) { 25 | _path = path.relative(path.dirname(options.output), _path); 26 | } 27 | } else { 28 | _path = first(options.lodash); 29 | } 30 | 31 | if (options.useNpmModules && chainBuild) { 32 | throw new Error('Cannot currently use npm modules with a library using chaining'); 33 | } 34 | 35 | // Otherwise compile a file for them to the modularization 36 | let config = lodash.chain(methods) 37 | .map(name => { 38 | for (var category in modules) { 39 | if (includes(modules[category], name)) { 40 | break; 41 | } 42 | } 43 | let realName = result(aliasToReal, name, name); 44 | return { 45 | name, 46 | propString: `${name}: ${name}`, 47 | path: options.useNpmModules ? 48 | `lodash.${realName.toLowerCase()}` : 49 | path.join(_path, category, realName), 50 | chained: chainableMethods[name] 51 | }; 52 | }) 53 | .partition(node => /\/chain\//.test(node.path)) 54 | .value(); 55 | 56 | let template = chainBuild ? chainTemplate : normalTemplate; 57 | let code = template({ 58 | config: config[1], 59 | chainMethods: config[0], 60 | lodashPath: _path 61 | }); 62 | 63 | let opts = { 64 | _evilES3SafeReExports: true, 65 | strict: false, 66 | name: options.global || '_' 67 | }; 68 | switch (options.exports) { 69 | case 'cjs': 70 | return esperanto.toCjs(code, opts); 71 | case 'amd': 72 | return esperanto.toAmd(code, opts); 73 | case 'umd': 74 | return esperanto.toUmd(code, opts); 75 | case 'es6': 76 | return {code}; 77 | } 78 | throw `Unsupported format: ${options.exports}`; 79 | } 80 | -------------------------------------------------------------------------------- /src/fs.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird'; 2 | export default Promise.promisifyAll(require('fs')); 3 | -------------------------------------------------------------------------------- /src/lodash-chainable.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | export default _.mapValues(_.prototype, (method, name) => { 4 | try { 5 | return _()[name]() instanceof _; 6 | } catch (e) { 7 | return false; 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /src/lodash-modularize.js: -------------------------------------------------------------------------------- 1 | import fs from './fs'; 2 | import lodashModules from './lodashModules'; 3 | import parseForModules from './parseForModules'; 4 | 5 | import {defaults, flatten, isArray, uniq} from 'lodash'; 6 | import Promise from 'bluebird'; 7 | const glob = Promise.promisify(require('glob')); 8 | 9 | import esperantoBuild from './esperanto-build'; 10 | import cliBuild from './cli-build'; 11 | 12 | const defaultOpts = require('../defaults.json'); 13 | 14 | export function resolve(files, options) { 15 | return Promise.map(files, file => { 16 | return fs.readFileAsync(file).then(blob => parseForModules(blob, file, options)); 17 | }) 18 | .then(methods => { 19 | return uniq(flatten(methods).sort(), true); 20 | }); 21 | } 22 | 23 | export default function modularize(fileGlob, options) { 24 | let files = (isArray(fileGlob) ? Promise.resolve(fileGlob) : glob(fileGlob)) 25 | .then(files => resolve(files, options)); 26 | 27 | options = defaults({}, options, defaultOpts); 28 | 29 | return Promise.all([files, lodashModules]) 30 | .spread((methods, modules) => { 31 | // What to do with the resulting methods (e.g. export, list, etc) 32 | if (options.list) { 33 | return methods; 34 | } 35 | let code; 36 | if (options.compile) { 37 | code = cliBuild(methods, options); 38 | } else { 39 | code = esperantoBuild(methods, modules, options).code; 40 | } 41 | let $code = Promise.resolve(code) 42 | .then($code => { 43 | code = String($code); 44 | return code; 45 | }); 46 | if (options.output) { 47 | $code = $code.then(code => fs.writeFileAsync(options.output, code)); 48 | } 49 | return $code.then(() => { 50 | return { 51 | code, 52 | methods 53 | }; 54 | }); 55 | }); 56 | } 57 | 58 | module.exports = modularize; 59 | -------------------------------------------------------------------------------- /src/lodashModules.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird'; 2 | import path from 'path'; 3 | import {zipObject} from 'lodash'; 4 | import fs from './fs'; 5 | 6 | function getDirectories(srcpath) { 7 | return fs.readdirAsync(srcpath).filter(file => 8 | fs.statAsync(path.join(srcpath, file)).then(stat => stat.isDirectory())); 9 | } 10 | 11 | const expectedPath = './node_modules/lodash'; 12 | const modularizePath = path.join(__dirname, '../node_modules/lodash'); 13 | 14 | let lodashPath = fs.existsSync(expectedPath) ? expectedPath : modularizePath; 15 | let m; 16 | 17 | export default getDirectories(lodashPath).then(modules => { 18 | m = modules; 19 | return Promise.map(modules, val => { 20 | return fs.readdirAsync(path.join(lodashPath, val)).map(name => name.slice(0, -3)); 21 | }); 22 | }) 23 | .then(functions => zipObject(m, functions)); 24 | -------------------------------------------------------------------------------- /src/parseForModules.js: -------------------------------------------------------------------------------- 1 | import {parse} from 'acorn'; 2 | import umd from 'acorn-umd'; 3 | import estraverse from 'estraverse'; 4 | import lodash, {compact, flatten, includes, map, reject} from 'lodash'; 5 | import {dirname, normalize, relative} from 'path'; 6 | 7 | import updateReferences from './updateReferences'; 8 | import Error from './Error'; 9 | 10 | const acornOptions = { 11 | ecmaVersion: 6, 12 | 13 | sourceType: 'module', 14 | 15 | // Be super loose (as who cares for this purpose) 16 | allowImportExportEverywhere: true, 17 | allowReturnOutsideFunction: true, 18 | allowHashBang: true 19 | }; 20 | 21 | function matchesPath(valids, path) { 22 | return includes(valids, path) || includes(valids, normalize(path)); 23 | } 24 | 25 | export function findModules(path, {imports, scope}) { 26 | let result = []; 27 | estraverse.traverse(scope, { 28 | enter(node) { 29 | switch (node.type) { 30 | case 'MemberExpression': 31 | if (includes(imports, node.object.name)) { 32 | if (node.computed) { 33 | let msg = `Could not id computed function ${node.object.name}[${node.property.name}]`; 34 | throw new Error(msg, path); 35 | } 36 | result.push(node.property.name); 37 | } 38 | break; 39 | case 'CallExpression': { // Detect chaining 40 | let callee = node; 41 | let props = []; 42 | while (callee = callee.callee) { 43 | if (callee.property) { 44 | props.push(callee.property.name); 45 | } 46 | if (callee.object) { 47 | callee = callee.object; 48 | } 49 | if (!callee.callee) { 50 | break; 51 | } 52 | } 53 | if (callee && includes(imports, callee.name)) { 54 | result.push(...props); 55 | } 56 | break; 57 | } 58 | } 59 | } 60 | }); 61 | return result; 62 | } 63 | 64 | export default function(code, path, options) { 65 | let ast = parse(code, lodash.assign({ 66 | ranges: true, 67 | locations: true 68 | }, acornOptions, lodash.result(options, 'acorn'))); 69 | 70 | let result = []; 71 | 72 | let outputFile = options.output && relative(dirname(path), options.output); 73 | 74 | // imports to consider lodash (e.g. lodash-compact, lodash, etc) 75 | let lodashOptions = compact(flatten([options.lodash, normalize, outputFile])); 76 | 77 | lodash(umd(ast, { 78 | amd: false, 79 | cjs: includes(options.format, 'cjs'), 80 | es6: includes(options.format, 'es6') 81 | })) 82 | .filter(node => { 83 | // consider adding lodash-fp & others 84 | return matchesPath(lodashOptions, node.source.value); 85 | }) 86 | .each(node => { 87 | // Add direct specifiers (`import {map, pick} from 'lodash'`) 88 | lodash(node.specifiers) 89 | .map('imported').compact() 90 | .each(requireNode => { 91 | result.push(requireNode.name); 92 | }).value(); 93 | }) 94 | .tap(nodes => { 95 | if (options.update && nodes.length) { 96 | updateReferences(code, path, nodes, options); 97 | } 98 | }) 99 | .map(node => { 100 | // filter the specifiers down to the direct imports 101 | // (handles `import x,{y,z} from 'foo';) 102 | return { 103 | imports: reject(node.specifiers, 'imported') 104 | .map(x => x.local.name), 105 | scope: node.scope.block 106 | }; 107 | }) 108 | .each(node => { 109 | result.push(...findModules(path, node)); 110 | }).value(); 111 | 112 | lodash(umd(ast, { 113 | amd: includes(options.format, 'amd'), 114 | cjs: false, 115 | es6: false 116 | })) 117 | .map(define => { 118 | let imports = define.imports.filter(specifier => 119 | matchesPath(lodashOptions, specifier[0].value)); 120 | if (imports.length) { 121 | return { 122 | imports, 123 | scope: define.scope, 124 | type: define.type 125 | }; 126 | } 127 | }) 128 | .compact() 129 | .tap(nodes => { 130 | let imports = flatten(nodes.map(nodes => map(nodes.imports, 0))); 131 | if (options.update && imports.length) { 132 | updateReferences(code, path, imports, options, nodes[0].type); 133 | } 134 | }) 135 | .map(node => { 136 | // filter the specifiers down to the direct imports 137 | // (handles `import x,{y,z} from 'foo';) 138 | return { 139 | imports: node.imports.map(zip => zip[1].name), 140 | scope: node.scope.block 141 | }; 142 | }) 143 | .each(node => { 144 | result.push(...findModules(path, node)); 145 | }).value(); 146 | 147 | if (typeof options.global === 'string') { 148 | result.push(...findModules(path, { 149 | imports: [options.global], 150 | scope: ast 151 | })); 152 | } 153 | 154 | return result; 155 | } 156 | -------------------------------------------------------------------------------- /src/updateReferences.js: -------------------------------------------------------------------------------- 1 | import {find, startsWith, transform} from 'lodash'; 2 | import recast from 'recast'; 3 | import path from 'path'; 4 | import fs from './fs'; 5 | 6 | import Error from './Error'; 7 | 8 | const builders = recast.types.builders; 9 | 10 | function replaceRequire(node, output) { 11 | return builders.callExpression(node.callee, [builders.literal(output)]); 12 | } 13 | 14 | export const updaters = { 15 | ImportDeclaration(path, node, output) { 16 | let source = builders.moduleSpecifier(output); 17 | let r = builders.importDeclaration(node.specifiers, source); 18 | path.replace(r); 19 | }, 20 | 21 | AMDImport(path, node, output) { 22 | path.replace(builders.literal(output)); 23 | }, 24 | 25 | CJSImport(path, node, output) { 26 | switch (node.type) { 27 | case 'VariableDeclarator': { 28 | let {id, init} = node; 29 | path.replace(builders.variableDeclarator(id, 30 | replaceRequire(init, output))); 31 | break; 32 | } 33 | // Annoyingly redundant 34 | case 'VariableDeclaration': { 35 | let {id, init} = node.declarations[0]; 36 | 37 | path.replace(builders.variableDeclaration(node.kind, [ 38 | builders.variableDeclarator(id, replaceRequire(init, output)) 39 | ])); 40 | break; 41 | } 42 | case 'AssignmentExpression': { 43 | let {operator, left, right} = node; 44 | path.replace(builders.assignmentExpression(operator, left, replaceRequire(right, output))); 45 | break; 46 | } 47 | case 'Property': { 48 | path.replace(builders.property(node.kind, node.key, replaceRequire(node.value, output))); 49 | break; 50 | } 51 | case 'CallExpression': { 52 | path.replace(replaceRequire(node, output)); 53 | break; 54 | } 55 | default: 56 | let msg = `${node.type} commonjs imports are not currently supported (file an issue)`; 57 | throw new Error(msg, output); 58 | } 59 | } 60 | }; 61 | 62 | // Update the import references from the source to point at the 63 | // new compiled file. 64 | export default function updateReferences(code, source, nodes, {output}) { 65 | output = path.relative(path.dirname(source), output); 66 | // Ensure requires work 67 | output = startsWith(output, path.normalize('../')) ? output : './' + output; 68 | 69 | let ast = recast.parse(code); 70 | let visitors = transform(nodes, (memo, nodeWrapper) => { 71 | let {type: format, reference: node} = nodeWrapper; 72 | let {type} = node; 73 | let updater = updaters[format]; 74 | memo[`visit${type}`] = function(path) { 75 | let node = path.value; 76 | let {start, end} = node.loc; 77 | // Find the corresponding import for this node 78 | let other = find(nodes, { 79 | reference: { 80 | type, loc: {start, end} 81 | } 82 | }); 83 | if (other) { 84 | updater(path, node, output); 85 | } 86 | this.traverse(path); 87 | }; 88 | }, {}); 89 | 90 | recast.visit(ast, visitors); 91 | fs.writeFile(source, recast.print(ast).code); 92 | } 93 | -------------------------------------------------------------------------------- /templates/chain-build.tpl: -------------------------------------------------------------------------------- 1 | import lodash from '<%= lodashPath %>/chain/lodash'; 2 | import LodashWrapper from '<%= lodashPath %>/internal/LodashWrapper'; 3 | 4 | import create from '<%= lodashPath %>/object/create'; 5 | import mixin from '<%= lodashPath %>/utility/mixin'; 6 | <% 7 | // Remove imported modules to prevent err 8 | _(config) 9 | .reject(_.partial(_.contains, ['create', 'mixin'])) 10 | .each(function(method) { 11 | %> 12 | import <%= method.name %> from '<%= method.path %>';<% }).value(); %> 13 | <% 14 | _.each(chainMethods, function(method) { 15 | %> 16 | import __<%= method.name %>__ from '<%= method.path %>';<% }); %> 17 | 18 | function _(value) { 19 | if (!(this instanceof _)) { 20 | return new _(value); 21 | } 22 | LodashWrapper.call(this, value); 23 | } 24 | _.prototype = create(lodash.prototype); 25 | 26 | // Add the methods used through chaining and explict use 27 | mixin(_, { 28 | <%= _(config).filter('chained').map('propString').join(',\n ') %> 29 | }, true); 30 | 31 | mixin(_, { 32 | <%= _(config).reject('chained').map('propString').join(',\n ') %> 33 | }, false); 34 | 35 | <% _.each(chainMethods, function(method) { %> 36 | _.prototype.<%= method.name %> = __<%= method.name %>__;<% }); %> 37 | 38 | export default _; -------------------------------------------------------------------------------- /templates/import-build.tpl: -------------------------------------------------------------------------------- 1 | <% _.each(config, function(method) { %> 2 | import <%= method.name %> from '<%= method.path %>';<% }); %> 3 | 4 | export default function lodash() { 5 | throw 'lodash chaining is not included in this build. Try rebuilding.'; 6 | } 7 | <% _.each(config, function(method) { %> 8 | lodash.<%= method.name %> = <%= method.name %>;<% }); %> -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise" : true, 3 | "camelcase" : true, 4 | "eqeqeq" : true, 5 | "forin" : false, 6 | "immed" : true, 7 | "indent" : 2, 8 | "latedef" : true, 9 | "newcap" : true, 10 | "noarg" : true, 11 | "nonbsp" : true, 12 | "nonew" : true, 13 | "plusplus" : false, 14 | "undef" : true, 15 | "unused" : true, 16 | "strict" : false, 17 | "maxparams" : 4, 18 | "maxdepth" : 2, 19 | "maxstatements" : 15, 20 | "maxcomplexity" : 10, 21 | "maxlen" : 200, 22 | 23 | "asi" : false, 24 | "boss" : false, 25 | "debug" : false, 26 | "eqnull" : false, 27 | "esnext" : true, 28 | "evil" : false, 29 | "expr" : true, 30 | "funcscope" : false, 31 | "globalstrict" : false, 32 | "iterator" : false, 33 | "lastsemic" : false, 34 | "loopfunc" : false, 35 | "maxerr" : 50, 36 | "notypeof" : false, 37 | "proto" : false, 38 | "scripturl" : false, 39 | "shadow" : false, 40 | "supernew" : false, 41 | "validthis" : false, 42 | "noyield" : false, 43 | 44 | "browser" : true, 45 | "couch" : false, 46 | "devel" : false, 47 | "dojo" : false, 48 | "jquery" : false, 49 | "mootools" : false, 50 | "node" : true, 51 | "nonstandard" : false, 52 | "prototypejs" : false, 53 | "rhino" : false, 54 | "worker" : false, 55 | "wsh" : false, 56 | "yui" : false, 57 | "globals": { 58 | "MyLibrary": true, 59 | "console": true, 60 | "sinon": true, 61 | "spy": true, 62 | "stub": true, 63 | "describe": true, 64 | "before": true, 65 | "after": true, 66 | "beforeEach": true, 67 | "afterEach": true, 68 | "it": true, 69 | "expect": true 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/samples/amd.js: -------------------------------------------------------------------------------- 1 | define(['lodash', 'smt', 'lodash-fp'], function(lo, s, fp) { 2 | lo.map([1,2,3], _.identity) 3 | }); 4 | 5 | define('global', ['lodash'], function(_) { 6 | _([234, 1, 1, [1, [1, 1]]]).uniq().flatten(); 7 | }); -------------------------------------------------------------------------------- /test/samples/chaining.js: -------------------------------------------------------------------------------- 1 | import _, {sortBy, uniq} from 'lodash'; 2 | let log = require('logger'), 3 | lodash = require('lodash'); 4 | 5 | let result = sortBy(_.flatten(uniq([{a: 1}, {a: 2}, {a: 1}, {a: 0}])), 'a'); 6 | lodash.each(result, log); 7 | 8 | lodash.chain([1, 2, 3]) 9 | .tap(function(array) { 10 | array.pop(); 11 | }) 12 | .map('x') 13 | .reverse() 14 | .value(); 15 | 16 | _([1]) 17 | .isEmpty() 18 | .toJSON(); 19 | -------------------------------------------------------------------------------- /test/samples/cjs.js: -------------------------------------------------------------------------------- 1 | var sortBy = require('lodash').sortBy, 2 | uniq = require('lodash').uniq; 3 | var log = require('logger'); 4 | var lodash = require('lodash'); 5 | 6 | let result = sortBy(lodash.flatten(uniq([{a: 1}, {a: 2}, {a: 1}, {a: 0}])), 'a'); 7 | lodash.each(result, log); 8 | -------------------------------------------------------------------------------- /test/samples/es6.js: -------------------------------------------------------------------------------- 1 | import _, {sortBy, uniq} from 'lodash'; 2 | import log from 'logger'; 3 | import lodash from 'lodash'; 4 | 5 | let result = sortBy(_.flatten(uniq([{a: 1}, {a: 2}, {a: 1}, {a: 0}])), 'a'); 6 | lodash.each(result, log); 7 | -------------------------------------------------------------------------------- /test/setup/node.js: -------------------------------------------------------------------------------- 1 | global.chai = require('chai'); 2 | global.sinon = require('sinon'); 3 | global.chai.use(require('sinon-chai')); 4 | 5 | require('babel/register'); 6 | require('./setup')(); 7 | -------------------------------------------------------------------------------- /test/setup/setup.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | global.expect = global.chai.expect; 3 | 4 | beforeEach(function() { 5 | this.sandbox = global.sinon.sandbox.create(); 6 | global.stub = this.sandbox.stub.bind(this.sandbox); 7 | global.spy = this.sandbox.spy.bind(this.sandbox); 8 | }); 9 | 10 | afterEach(function() { 11 | delete global.stub; 12 | delete global.spy; 13 | this.sandbox.restore(); 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /test/tmp/es6.js: -------------------------------------------------------------------------------- 1 | 2 | import each from 'lodash/collection/each'; 3 | import flatten from 'lodash/array/flatten'; 4 | import sortBy from 'lodash/collection/sortBy'; 5 | import uniq from 'lodash/array/uniq'; 6 | 7 | export default function lodash() { 8 | throw 'lodash chaining is not included in this build. Try rebuilding.'; 9 | } 10 | 11 | lodash.each = each; 12 | lodash.flatten = flatten; 13 | lodash.sortBy = sortBy; 14 | lodash.uniq = uniq; -------------------------------------------------------------------------------- /test/unit/index.js: -------------------------------------------------------------------------------- 1 | import modularize from '../../src/lodash-modularize'; 2 | import path from 'path'; 3 | // import fs from 'fs'; 4 | import {parse} from 'acorn'; 5 | import umd from 'acorn-umd'; 6 | 7 | const samplePath = path.join(__dirname, '../samples/'); 8 | function filePath(p) { 9 | return [path.join(samplePath, p)]; 10 | } 11 | 12 | describe('Listing support', () => { 13 | it('works on ES6 modules', (done) => { 14 | modularize(filePath('es6.js'), { 15 | list: true 16 | }) 17 | .then(modules => { 18 | expect(modules).to.deep.equal(['each', 'flatten', 'sortBy', 'uniq']); 19 | }) 20 | .then(done, done); 21 | }); 22 | 23 | it('works on CJS modules', (done) => { 24 | modularize(filePath('cjs.js'), { 25 | list: true 26 | }) 27 | .then(modules => { 28 | expect(modules).to.deep.equal(['each', 'flatten', 'sortBy', 'uniq']); 29 | }) 30 | .then(done, done); 31 | }); 32 | 33 | it('works on AMD modules', (done) => { 34 | modularize(filePath('amd.js'), { 35 | list: true 36 | }) 37 | .then(modules => { 38 | expect(modules).to.deep.equal(['flatten', 'identity', 'map', 'uniq']); 39 | }) 40 | .then(done, done); 41 | }); 42 | 43 | it('supports chaining', (done) => { 44 | modularize(filePath('chaining.js'), { 45 | list: true 46 | }) 47 | .then(modules => { 48 | expect(modules).to.deep.equal(['chain', 'each', 'flatten', 'isEmpty', 'map', 'reverse', 'sortBy', 'tap', 'toJSON', 'uniq', 'value']); 49 | }) 50 | .then(done, done); 51 | }); 52 | }); 53 | 54 | describe('Basic transform produces valid JS', () => { 55 | it('on ES6 modules', (done) => { 56 | modularize(filePath('es6.js'), {exports: 'es6'}) 57 | .then(({code}) => { 58 | let ast = parse(code, {ecmaVersion: 6, sourceType: 'module'}); 59 | let modules = umd(ast, {es6: true, amd: false, cjs: false}); 60 | expect(modules).to.have.length(4); 61 | }) 62 | .then(done, done); 63 | }); 64 | 65 | it('on CJS modules', (done) => { 66 | modularize(filePath('cjs.js')) 67 | .then(({code}) => { 68 | let ast = parse(code, {ecmaVersion: 6, sourceType: 'module'}); 69 | let modules = umd(ast, {es6: false, amd: false, cjs: true}); 70 | expect(modules).to.have.length(4); 71 | modules = umd(ast, {es6: false, amd: true, cjs: false}); 72 | expect(modules).to.have.length(0); 73 | }) 74 | .then(done, done); 75 | }); 76 | 77 | it('on chaining', (done) => { 78 | modularize(filePath('chaining.js')) 79 | .then(({code}) => { 80 | let ast = parse(code, {ecmaVersion: 6, sourceType: 'module'}); 81 | let modules = umd(ast, {es6: false, amd: false, cjs: true}); 82 | expect(modules).to.have.length(14); 83 | modules = umd(ast, {es6: true, amd: true, cjs: true}); 84 | expect(modules).to.have.length(14); 85 | modules = umd(ast, {es6: true, amd: true, cjs: false}); 86 | expect(modules).to.have.length(0); 87 | }) 88 | .then(done, done); 89 | }); 90 | }); 91 | 92 | describe('Output', () => { 93 | it('on ES6 modules', (done) => { 94 | modularize(filePath('es6.js'), { 95 | exports: 'es6', 96 | output: path.join(__dirname, '../tmp/es6-lodash.js') 97 | }) 98 | .then(() => { 99 | const _ = require('../tmp/es6-lodash'); 100 | expect(_).to.have.keys(['each', 'flatten', 'sortBy', 'uniq']); 101 | }) 102 | .then(done, done); 103 | }); 104 | }); 105 | 106 | describe('Lodash CLI compile', () => { 107 | it('on ES6 modules', function(done) { 108 | this.timeout(5000); 109 | modularize(filePath('es6.js'), {compile: true}) 110 | .then(({code}) => { 111 | let ast = parse(code, {ecmaVersion: 6, sourceType: 'module'}); 112 | let modules = umd(ast, {es6: true, amd: true, cjs: true}); 113 | expect(modules).to.have.length(0); 114 | }) 115 | .then(done, done); 116 | }); 117 | }); 118 | --------------------------------------------------------------------------------