├── packages ├── babel-minify │ ├── test │ │ ├── resources │ │ │ └── src │ │ │ │ ├── baz.js │ │ │ │ ├── foo │ │ │ │ ├── a.js │ │ │ │ ├── b.js │ │ │ │ └── c.js │ │ │ │ └── bar.js │ │ ├── index.js │ │ └── cli.js │ ├── src │ │ ├── cli.js │ │ ├── defaults.js │ │ ├── cli-yargs.js │ │ ├── cli-runner.js │ │ └── index.js │ ├── package.json │ └── README.md ├── babel-preset-min │ ├── test │ │ └── index.js │ ├── index.js │ ├── package.json │ └── README.md ├── gulp-babel-minify │ ├── README.md │ ├── src │ │ └── index.js │ └── package.json ├── babel-plugin-transform-mangle │ ├── src │ │ ├── namegen.js │ │ └── index.js │ ├── package.json │ ├── test │ │ ├── namegen.js │ │ └── index.js │ └── README.md ├── babel-plugin-transform-conditionals │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.js │ │ ├── extract-vars.js │ │ └── replace-with.js │ └── test │ │ └── index.js ├── babel-plugin-transform-evaluate │ ├── README.md │ ├── package.json │ ├── test │ │ └── index.js │ └── src │ │ └── index.js ├── babel-plugin-transform-global-defs │ ├── README.md │ ├── package.json │ ├── test │ │ └── index.js │ └── src │ │ └── index.js └── babel-plugin-transform-function-to-arrow │ ├── package.json │ ├── README.md │ ├── src │ └── index.js │ └── test │ └── index.js ├── .eslintignore ├── .babelrc ├── .gitignore ├── utils ├── browser.js ├── mocha-require.js ├── mocha.opts ├── index.js ├── test.eslintrc ├── publish.js └── clean-empty-modules.js ├── .flowconfig ├── lerna.json ├── .editorconfig ├── .travis.yml ├── .tern-project ├── .eslintrc ├── declarations ├── babel-minify.js ├── babel.js └── yargs.js ├── package.json ├── CONTRIBUTING.md ├── gulpfile.js ├── CODE_OF_CONDUCT.md └── README.md /packages/babel-minify/test/resources/src/baz.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules/** 2 | build/** 3 | -------------------------------------------------------------------------------- /packages/babel-minify/test/resources/src/foo/a.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/babel-minify/test/resources/src/foo/b.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/babel-minify/test/resources/src/foo/c.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015-node4"], 3 | "plugins": ["add-module-exports"] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log* 3 | *~ 4 | lib 5 | lerna-debug.log* 6 | build 7 | .vscode 8 | .DS_Store 9 | -------------------------------------------------------------------------------- /utils/browser.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import BabelMinify from '../packages/babel-minify'; 3 | 4 | module.exports = BabelMinify; 5 | -------------------------------------------------------------------------------- /packages/babel-minify/test/resources/src/bar.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | let foo = 1; 3 | let bar = 2; 4 | window.baz = foo + bar; 5 | })(); 6 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | ./packages/*/src/*.js 5 | ./packages/*/*.js 6 | 7 | [libs] 8 | declarations 9 | 10 | [options] 11 | -------------------------------------------------------------------------------- /utils/mocha-require.js: -------------------------------------------------------------------------------- 1 | global.expect = require('expect'); 2 | global.transform = require('babel-core').transform; 3 | global.trim = require('./').trim; 4 | -------------------------------------------------------------------------------- /utils/mocha.opts: -------------------------------------------------------------------------------- 1 | --compilers js:babel-core/register 2 | --require utils/mocha-require.js 3 | --reporter dot 4 | --ui tdd 5 | --timeout 10000 6 | packages/*/test/*.js 7 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.0.0-beta.23", 3 | "version": "independent", 4 | "publishConfig": { 5 | "ignore": [ 6 | "test/**" 7 | ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [**] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /utils/index.js: -------------------------------------------------------------------------------- 1 | // utils for tests 2 | export function trim(str) { 3 | return str.replace(/[;\s\f\r\n\t\v]/g, ''); 4 | } 5 | export function compare(code1, code2) { 6 | return trim(code1) === trim(code2); 7 | } 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | - '5' 5 | - '4' 6 | cache: 7 | directories: 8 | - node_modules 9 | install: 10 | - npm install 11 | - npm run bootstrap 12 | script: 13 | - npm run build 14 | - npm run lint 15 | - npm run flow 16 | - npm run test 17 | -------------------------------------------------------------------------------- /packages/babel-preset-min/test/index.js: -------------------------------------------------------------------------------- 1 | import preset from '../'; 2 | 3 | describe('babel-preset-min', function() { 4 | it('should contain an array of plugins', function() { 5 | expect(preset.plugins).toBeAn('array'); 6 | expect(preset.plugins.length).toBeGreaterThanOrEqualTo(1); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /.tern-project: -------------------------------------------------------------------------------- 1 | { 2 | "libs": [], 3 | "loadEagerly": [ 4 | "./packages/*/src/*.js", 5 | "./utils/*.js", 6 | "./packages/*/test/*.js", 7 | "./*.js" 8 | ], 9 | "plugins": { 10 | "requirejs": { 11 | "baseURL": "./", 12 | "paths": {} 13 | }, 14 | "es_modules": {} 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/babel-preset-min/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | const pluginsAndPresets /*:MinifierResult*/ = require('babel-minify')(null, { minify: false }); 3 | 4 | let plugins = [].concat(pluginsAndPresets.plugins); 5 | 6 | pluginsAndPresets.presets.forEach(preset => { 7 | plugins = [...plugins, ...preset.plugins]; 8 | }); 9 | 10 | module.exports = { plugins }; 11 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "browser": true, 5 | "es6": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 6, 9 | "sourceType": "module" 10 | }, 11 | "extends": "eslint:recommended", 12 | "rules": { 13 | "comma-dangle": 0, 14 | "no-warning-comments": 1, 15 | "no-unused-vars": 2, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/gulp-babel-minify/README.md: -------------------------------------------------------------------------------- 1 | # gulp-babel-minify 2 | 3 | ## Install 4 | 5 | ``` 6 | npm install gulp-babel-minify --save-dev 7 | ``` 8 | 9 | ## Usage 10 | 11 | ```js 12 | import minify from 'gulp-babel-minify'; 13 | 14 | gulp.task('min', function() { 15 | return gulp.src('build/temp/app.bundle.js') 16 | .pipe(minify(opts)) 17 | .pipe(gulp.dest('build/')); 18 | }) 19 | ``` 20 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-mangle/src/namegen.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | export default function* nameGenerator() /*:Iterator*/ { 3 | const atoz = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 4 | let i = 0; 5 | /* eslint-disable no-constant-condition */ 6 | while (true) { 7 | /* eslint-enable */ 8 | if (i) for (let j of atoz) yield j+i; 9 | else yield* atoz; 10 | i++; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-conditionals/README.md: -------------------------------------------------------------------------------- 1 | # babel-plugin-transform-conditionals 2 | 3 | Transform conditional expressions and remove dead code. 4 | 5 | ## Install 6 | 7 | ``` 8 | npm install babel-plugin-transform-conditionals 9 | ``` 10 | 11 | **In** 12 | 13 | ```js 14 | if (false) { 15 | doSomething(); 16 | } else { 17 | doSomethingElse(); 18 | } 19 | ``` 20 | 21 | **Out** 22 | 23 | ```js 24 | doSomethingElse() 25 | ``` 26 | -------------------------------------------------------------------------------- /utils/test.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "mocha": true 5 | }, 6 | globals: { 7 | "expect": true, 8 | "transform": true, 9 | "trim": true 10 | }, 11 | "parserOptions": { 12 | "ecmaVersion": 6, 13 | "sourceType": "module" 14 | }, 15 | "extends": "eslint:recommended", 16 | "rules": { 17 | "comma-dangle": 0, 18 | "no-warning-comments": 1, 19 | "no-unused-vars": 2, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/gulp-babel-minify/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import through2 from 'through2'; 3 | import babelMinify from 'babel-minify'; 4 | 5 | export default function GulpBabelMinify(opts /*:MinifierOptions*/) { 6 | return through2.obj(function(file, enc, callback) { 7 | const inputCode = file.contents.toString(); 8 | const output = babelMinify(inputCode, opts); 9 | 10 | file.contents = new Buffer(output); 11 | callback(null, file); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-evaluate/README.md: -------------------------------------------------------------------------------- 1 | # babel-plugin-transform-evaluate 2 | 3 | Attempt evaluating constant expressions 4 | 5 | ## Install 6 | 7 | ``` 8 | npm install babel-plugin-transform-evaluate 9 | ``` 10 | 11 | **In** 12 | 13 | ```js 14 | const x = Math.floor(6.5) + 1 * 2 / 3 * 6 % 5 + myValue; 15 | const y = true && false || "default value"; 16 | ``` 17 | 18 | **Out** 19 | 20 | ```js 21 | const x = 10 + myValue; 22 | const y = "default value"; 23 | ``` 24 | -------------------------------------------------------------------------------- /packages/babel-preset-min/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-preset-min", 3 | "version": "0.1.15", 4 | "description": "A Preset containing default optimizations defined in babel-minify", 5 | "main": "index.js", 6 | "author": "boopathi", 7 | "license": "MIT", 8 | "dependencies": { 9 | "babel-minify": "^0.1.12" 10 | }, 11 | "homepage": "https://github.com/boopathi/babel-minify", 12 | "repository": "https://github.com/boopathi/babel-minify/tree/master/packages/babel-preset-min" 13 | } 14 | -------------------------------------------------------------------------------- /packages/gulp-babel-minify/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-babel-minify", 3 | "version": "0.1.12", 4 | "description": "Gulp plugin for babel-minify", 5 | "main": "lib/index.js", 6 | "author": "boopathi", 7 | "license": "MIT", 8 | "dependencies": { 9 | "babel-minify": "^0.1.12", 10 | "through2": "^2.0.1" 11 | }, 12 | "homepage": "https://github.com/boopathi/babel-minify", 13 | "repository": "https://github.com/boopathi/babel-minify/tree/master/packages/gulp-babel-minify" 14 | } 15 | -------------------------------------------------------------------------------- /packages/babel-minify/src/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // @flow 3 | /*::import type {Argv} from 'yargs'*/ 4 | import yargs from './cli-yargs'; 5 | import runner from './cli-runner'; 6 | 7 | function run(argvRaw /*:string[]*/, opts /*:CliRunnerOptions*/) { 8 | const argv /*:Argv*/= yargs(argvRaw); 9 | runner(argv, opts); 10 | } 11 | 12 | module.exports = run; 13 | 14 | // Just so I can test simply by importing it 15 | // and not having to mess around child_process 16 | // which also turns out to be really slow for tests 17 | if ((require.main /*:any*/) === module) { 18 | run(process.argv.slice(2), { 19 | logger: console 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-mangle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-transform-mangle", 3 | "version": "0.1.7", 4 | "description": "Converts var longname; to var a;", 5 | "main": "lib/index.js", 6 | "author": "boopathi", 7 | "license": "MIT", 8 | "homepage": "https://github.com/boopathi/babel-minify", 9 | "repository": "https://github.com/boopathi/babel-minify/tree/master/packages/babel-plugin-transform-mangle", 10 | "browserify": { 11 | "transform": [ 12 | [ 13 | "babelify", 14 | { 15 | "presets": [ 16 | "es2015" 17 | ] 18 | } 19 | ] 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-evaluate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-transform-evaluate", 3 | "version": "0.1.8", 4 | "description": "Evaluate constant expressions", 5 | "main": "lib/index.js", 6 | "author": "boopathi", 7 | "license": "MIT", 8 | "homepage": "https://github.com/boopathi/babel-minify", 9 | "repository": "https://github.com/boopathi/babel-minify/tree/master/packages/babel-plugin-transform-evaluate", 10 | "browserify": { 11 | "transform": [ 12 | [ 13 | "babelify", 14 | { 15 | "presets": [ 16 | "es2015" 17 | ] 18 | } 19 | ] 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-conditionals/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-transform-conditionals", 3 | "version": "0.1.2", 4 | "description": "Optimize conditional statements", 5 | "main": "lib/index.js", 6 | "author": "boopathi", 7 | "license": "MIT", 8 | "homepage": "https://github.com/boopathi/babel-minify", 9 | "repository": "https://github.com/boopathi/babel-minify/tree/master/packages/babel-plugin-transform-conditionals", 10 | "browserify": { 11 | "transform": [ 12 | [ 13 | "babelify", 14 | { 15 | "presets": [ 16 | "es2015" 17 | ] 18 | } 19 | ] 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-global-defs/README.md: -------------------------------------------------------------------------------- 1 | # babel-plugin-transform-global-defs 2 | 3 | Transform Global definitions that are available at build time 4 | 5 | ## Install 6 | 7 | ``` 8 | npm install babel-plugin-transform-global-defs 9 | ``` 10 | 11 | ## Options 12 | 13 | ```json 14 | { 15 | "global_defs": { 16 | "process": { 17 | "env": { 18 | "NODE_ENV": "production" 19 | } 20 | } 21 | } 22 | } 23 | ``` 24 | 25 | **In** 26 | 27 | ```js 28 | if (process.env.NODE_ENV !== 'production') { 29 | DEBUG = true; 30 | } 31 | ``` 32 | 33 | **Out** 34 | 35 | ```js 36 | if ('production' !== 'production') { 37 | DEBUG = true; 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-function-to-arrow/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-transform-function-to-arrow", 3 | "version": "0.1.1", 4 | "description": "Converts function expressions to arrow expressions", 5 | "main": "lib/index.js", 6 | "author": "boopathi", 7 | "license": "MIT", 8 | "homepage": "https://github.com/boopathi/babel-minify", 9 | "repository": "https://github.com/boopathi/babel-minify/tree/master/packages/babel-plugin-transform-function-to-arrow", 10 | "browserify": { 11 | "transform": [ 12 | [ 13 | "babelify", 14 | { 15 | "presets": [ 16 | "es2015" 17 | ] 18 | } 19 | ] 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-conditionals/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /*::import type {NodePath, Binding, Scope, Node, PluginOptions} from 'Babel';*/ 3 | import replaceWith from './replace-with'; 4 | 5 | export default function Conditionals({types: t} /*:PluginOptions*/) { 6 | return { 7 | visitor: { 8 | Conditional(path /* :NodePath */) { 9 | const evaluated = path.get('test').evaluate(); 10 | if (!evaluated.confident) { 11 | return path.skip(); 12 | } 13 | if (evaluated.value) { 14 | replaceWith('consequent', path, t); 15 | } else { 16 | replaceWith('alternate', path, t); 17 | } 18 | } 19 | } 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-mangle/test/namegen.js: -------------------------------------------------------------------------------- 1 | import nameGenerator from '../src/namegen'; 2 | 3 | describe('babel-plugin-transform-mangle', function() { 4 | it('should work for a small sample of 200', function() { 5 | const gen = nameGenerator(); 6 | let i = 0, limit = 250; 7 | 8 | function verify(x, y, character, value) { 9 | expect(x ? character + x : character).toEqual(value); 10 | } 11 | 12 | do { 13 | let x = parseInt(i / 52); 14 | let y = i % 52; 15 | let next = gen.next().value; 16 | switch (y) { 17 | case 0: 18 | verify(x, y, 'a', next); 19 | break; 20 | case 13: 21 | verify(x, y, 'n', next); 22 | break; 23 | } 24 | } while (i++ < limit); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-global-defs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-transform-global-defs", 3 | "version": "0.1.1", 4 | "description": "Transform global definitions that are available at build time", 5 | "main": "lib/index.js", 6 | "author": "boopathi", 7 | "license": "MIT", 8 | "homepage": "https://github.com/boopathi/babel-minify", 9 | "repository": "https://github.com/boopathi/babel-minify/tree/master/packages/babel-plugin-transform-global-defs", 10 | "browserify": { 11 | "transform": [ 12 | [ 13 | "babelify", 14 | { 15 | "presets": [ 16 | "es2015" 17 | ] 18 | } 19 | ] 20 | ] 21 | }, 22 | "dependencies": { 23 | "lodash.isobjectlike": "^4.0.0", 24 | "lodash.isplainobject": "^4.0.4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-function-to-arrow/README.md: -------------------------------------------------------------------------------- 1 | # babel-plugin-transform-function-to-arrow 2 | 3 | Transforms functions to arrows and simplifies arrow functions with single return statement 4 | 5 | ## Install 6 | 7 | ``` 8 | npm install babel-plugin-transform-function-to-arrow 9 | ``` 10 | 11 | **In** 12 | 13 | ```js 14 | const x = function (a, b) { return a + b } 15 | const y = (x) => { 16 | return Math.sin(x); 17 | } 18 | const z = function () { 19 | return arguments[0]; 20 | } 21 | ``` 22 | 23 | **Out** 24 | 25 | ```js 26 | const x = (a,b) => a+b; 27 | const y = x => Math.sin(x); 28 | const z = function () { 29 | return arguments[0]; 30 | } 31 | ``` 32 | 33 | ## Options 34 | 35 | + `keep_fnames`: [Default: false] Don't transform functions to arrows for FunctionExpressions with names - Useful for code depending on `fn.name`. 36 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-mangle/README.md: -------------------------------------------------------------------------------- 1 | # babel-plugin-transform-mangle 2 | 3 | Mangle Identifiers 4 | 5 | ## Install 6 | 7 | ``` 8 | npm install babel-plugin-transform-mangle --save-dev 9 | ``` 10 | ## Example 11 | 12 | **In :** 13 | 14 | ```js 15 | import MyAwesomeLib from 'my-awesome-lib'; 16 | const ReallyLongName = "1"; 17 | class BlahBlahBlahBlah { 18 | method() {} 19 | } 20 | function doSomethingWithAReallyLongName() { 21 | var localVariable, someIdentifier; 22 | } 23 | ``` 24 | 25 | **Out :** 26 | 27 | ```js 28 | import a from 'my-awesome-lib'; 29 | const b = "1"; 30 | class c { 31 | method() {} 32 | } 33 | function d() { 34 | var a, b; 35 | } 36 | ``` 37 | 38 | ## Options 39 | 40 | + `keep_fnames`: [Default: false] Don't mangle function names for FunctionExpressions and FunctionDeclarations - Useful for code depending on `fn.name` 41 | 42 | + `topLevel`: [Default: false] Mangle variables in the outermost scope 43 | 44 | + `eval`: [Default: false] Don't deopt from mangling when eval is found in the subtree 45 | 46 | + `except`: [Default: []] Pass in an array of strings or functions or RegExps to match against the variable name that is mangled. If there is a match, then that variable name will NOT be mangled. 47 | -------------------------------------------------------------------------------- /packages/babel-preset-min/README.md: -------------------------------------------------------------------------------- 1 | # babel-preset-min 2 | 3 | This is a preset that uses the default options of [babel-minify](https://github.com/boopathi/babel-minify/tree/master/packages/babel-minify) 4 | 5 | Check [babel-minify#options](https://github.com/boopathi/babel-minify/tree/master/packages/babel-minify#options) to find the default transformations applied or to find what exactly this preset will do. 6 | 7 | **WARNING:** This might cause some regression, depending on what other plugins and presets you use with this preset - because all the plugins are applied in one pass by default in babel. You can enable the `passPerPreset` option in babel, but then all the `babel-minify` plugins are still applied in one pass. So, consider using `babel-minify` NodeAPI or CLI or Gulp task with the [options](https://github.com/boopathi/babel-minify/tree/master/packages/babel-minify#options) - `plugins: []` and `presets: []` to pass your other plugins and presets. 8 | 9 | ## Install 10 | 11 | ``` 12 | npm install babel-preset-min --save-dev 13 | ``` 14 | 15 | ### .babelrc 16 | 17 | ```json 18 | { 19 | "presets": ["min"], 20 | "comments": false, 21 | "compact": true, 22 | "minified": true, 23 | "passPerPreset": true 24 | } 25 | ``` 26 | -------------------------------------------------------------------------------- /packages/babel-minify/src/defaults.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | const D /*:MinifierOptions*/ = { 3 | mangle : true, 4 | 5 | dead_code : false, 6 | conditionals : true, 7 | evaluate : true, 8 | drop_debugger : false, 9 | drop_console : false, 10 | properties : true, 11 | join_vars : true, 12 | booleans : true, 13 | unsafe : true, 14 | keep_fnames : false, 15 | 16 | // global-defs 17 | global_defs : {}, 18 | 19 | // source maps 20 | sourceMaps : false, 21 | 22 | // input sourcemap 23 | inputSourceMap : null, 24 | 25 | // number of passes 26 | passes : 1, 27 | 28 | // passed on to babel transform to tell whether to use babelrc 29 | babelrc : false, 30 | 31 | // should there be any other plugins added to this build process 32 | plugins : [], 33 | 34 | // should there be any other presets 35 | presets : [], 36 | 37 | // if false, babel-minify can give a list of plugins to use as a preset 38 | minify : true, 39 | }; 40 | 41 | export default D; 42 | 43 | export const mangleDefaults /*:MangleOptions*/ = { 44 | keep_fnames : false, 45 | eval : false, 46 | except : [], 47 | topLevel : false, 48 | } 49 | -------------------------------------------------------------------------------- /declarations/babel-minify.js: -------------------------------------------------------------------------------- 1 | import type {Plugin, Preset, BabelResult} from 'Babel'; 2 | 3 | declare type MinifierOptions = { 4 | mangle: bool | MangleOptions, 5 | dead_code: bool, 6 | conditionals: bool, 7 | evaluate: bool, 8 | drop_debugger: bool, 9 | drop_console: bool, 10 | properties: bool, 11 | join_vars: bool, 12 | booleans: bool, 13 | unsafe: bool, 14 | keep_fnames: bool, 15 | global_defs: Object, 16 | passes: number, 17 | babelrc: bool, 18 | plugins: Plugin[], 19 | presets: Preset[], 20 | minify: bool, 21 | 22 | sourceMaps: boolean, 23 | inputSourceMap: Object | null, 24 | } 25 | 26 | declare type MangleOptions = { 27 | topLevel: bool, 28 | eval: bool, 29 | except: [string|Object|function], 30 | keep_fnames: bool, 31 | } 32 | 33 | declare type ManglePluginOptions = { 34 | opts: MangleOptions 35 | } 36 | 37 | declare type MinifierResult = { 38 | plugins: Plugin[], 39 | presets: Preset[], 40 | code?: string, 41 | sourceMap?: string, 42 | } 43 | 44 | declare type MinifierOutput = BabelResult | MinifierResult 45 | 46 | declare type GlobalDefsOptions = { 47 | opts: { 48 | global_defs: Object, 49 | } 50 | } 51 | 52 | declare type FunctionToArrowOptions = { 53 | opts: { 54 | keep_fnames: boolean, 55 | } 56 | } 57 | 58 | declare type CliRunnerOptions = { 59 | logger: { 60 | log: function, 61 | error: function, 62 | warn: function, 63 | } 64 | } 65 | 66 | declare type CliTaskResult = { 67 | contents: string, 68 | filename: string 69 | } 70 | -------------------------------------------------------------------------------- /packages/babel-minify/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-minify", 3 | "version": "0.1.12", 4 | "description": "Babel minify Node API and CLI", 5 | "main": "lib/index.js", 6 | "author": "boopathi", 7 | "license": "MIT", 8 | "bin": { 9 | "babel-minify": "lib/cli.js" 10 | }, 11 | "dependencies": { 12 | "babel-core": "^6.10.4", 13 | "babel-plugin-transform-conditionals": "^0.1.2", 14 | "babel-plugin-transform-dead-code-elimination": "^2.0.1", 15 | "babel-plugin-transform-evaluate": "^0.1.8", 16 | "babel-plugin-transform-function-to-arrow": "^0.1.1", 17 | "babel-plugin-transform-global-defs": "^0.1.1", 18 | "babel-plugin-transform-mangle": "^0.1.7", 19 | "babel-plugin-transform-member-expression-literals": "^6.8.0", 20 | "babel-plugin-transform-merge-sibling-variables": "^6.8.0", 21 | "babel-plugin-transform-minify-booleans": "^6.8.0", 22 | "babel-plugin-transform-property-literals": "^6.8.0", 23 | "babel-plugin-transform-remove-console": "^6.8.0", 24 | "babel-plugin-transform-remove-debugger": "^6.8.0", 25 | "babel-plugin-transform-simplify-comparison-operators": "^6.8.0", 26 | "babel-plugin-transform-undefined-to-void": "^6.8.0", 27 | "mkdirp": "^0.5.1", 28 | "yargs": "^4.7.1" 29 | }, 30 | "homepage": "https://github.com/boopathi/babel-minify", 31 | "repository": "https://github.com/boopathi/babel-minify/tree/master/packages/babel-minify", 32 | "browserify": { 33 | "transform": [ 34 | [ 35 | "babelify", 36 | { 37 | "presets": [ 38 | "es2015" 39 | ] 40 | } 41 | ] 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-minify", 3 | "description": "Attempt at writing minify story for ES2015+", 4 | "private": true, 5 | "scripts": { 6 | "bootstrap": "lerna bootstrap", 7 | "build": "npm run clean && gulp build", 8 | "clean": "gulp clean", 9 | "flow": "flow check", 10 | "lint": "npm run lint-src && npm run lint-test && npm run lint-others", 11 | "lint-src": "eslint -c .eslintrc packages/*/src packages/*/*.js", 12 | "lint-test": "eslint -c utils/test.eslintrc packages/*/test --ignore-pattern '**/packages/*/test/resources/**'", 13 | "lint-others": "eslint -c .eslintrc utils/ *.js", 14 | "gh-pages": "gulp gh-pages", 15 | "watch": "npm run clean && gulp watch", 16 | "test": "mocha --opts utils/mocha.opts --grep ${TEST_GREP-babel}" 17 | }, 18 | "devDependencies": { 19 | "babel-core": "^6.10.4", 20 | "babel-plugin-add-module-exports": "^0.2.1", 21 | "babel-polyfill": "^6.9.1", 22 | "babel-preset-es2015": "^6.9.0", 23 | "babel-preset-es2015-node4": "^2.1.0", 24 | "babelify": "^7.3.0", 25 | "browserify": "^13.0.1", 26 | "eslint": "^3.0.0", 27 | "expect": "^1.20.2", 28 | "flow-bin": "0.28.0", 29 | "gulp": "^3.9.1", 30 | "gulp-babel": "^6.1.2", 31 | "gulp-copy": "0.0.2", 32 | "gulp-newer": "^1.2.0", 33 | "gulp-plumber": "^1.1.0", 34 | "gulp-uglify": "^2.0.0", 35 | "gulp-util": "^3.0.7", 36 | "gulp-watch": "^4.3.8", 37 | "lerna": "2.0.0-beta.23", 38 | "mocha": "^3.0.0", 39 | "rimraf": "^2.5.2", 40 | "through2": "^2.0.1", 41 | "vinyl-buffer": "^1.0.0", 42 | "vinyl-source-stream": "^1.1.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-evaluate/test/index.js: -------------------------------------------------------------------------------- 1 | import evaluate from '../src'; 2 | 3 | function test(input) { 4 | return trim(transform(input, { 5 | plugins: [evaluate], 6 | babelrc: false, 7 | }).code); 8 | } 9 | 10 | describe('babel-plugin-transform-evaluate', function () { 11 | it('should transform binary expressions', function () { 12 | expect( 13 | test('var x = 1 + 2 + 3 + 4 % 5;') 14 | ).toEqual( 15 | trim('var x = 10') 16 | ); 17 | }); 18 | 19 | it('should transform logical expressions', function () { 20 | expect( 21 | test('const y = true && false || "default value";') 22 | ).toEqual( 23 | trim('const y = "default value"') 24 | ); 25 | }); 26 | 27 | it('should transform simple call expressions', function () { 28 | expect( 29 | test(` 30 | var x = 1+2+3+4%5+Math.ceil(4.5); 31 | function y() { return 4 }; 32 | y(); 33 | `) 34 | ).toEqual( 35 | trim(` 36 | var x = 15; 37 | function y() {return 4}; 38 | y(); 39 | `) 40 | ); 41 | }); 42 | 43 | it('should not transform (1, func)(something) to func(something)', function () { 44 | expect( 45 | test('(1, eval)("x=4")') 46 | ).toEqual( 47 | trim('(1, eval)("x=4")') 48 | ); 49 | }); 50 | 51 | it('should not fail to deopt for object and array pattern', function () { 52 | expect( 53 | test('var x = 5; var [y] = [x]; { x = 6 }') 54 | ).toEqual( 55 | trim('var x = 5; var [y] = [x]; { x = 6 }') 56 | ); 57 | }); 58 | 59 | it('should not replace when the evaluated result is longer than source', function () { 60 | expect( 61 | test('1/3') 62 | ).toEqual( 63 | trim('1/3') 64 | ); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /utils/publish.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This exists because README is not published to npm when doing 3 | * lerna publish. And it is a bug upstream with npm and not with lerna 4 | * 5 | * https://github.com/lerna/lerna/issues/64 6 | * https://github.com/npm/newww/issues/389 7 | */ 8 | 9 | const lerna = require('lerna'); 10 | const npm = require('lerna/lib/NpmUtilities'); 11 | // const git = require('lerna/lib/GitUtilities'); 12 | 13 | // function runVersion(cb) { 14 | // const PublishCommand = lerna.__commands__.publish; 15 | // const flags = { 16 | // skipNpm: true, 17 | // skipGit: true, 18 | // }; 19 | // const publish = new PublishCommand([], flags); 20 | // publish.runValidations(); 21 | // publish.runPreparations(); 22 | // publish._attempt('initialize', () => { 23 | // publish._attempt('execute', () => { 24 | // cb(); 25 | // }); 26 | // }); 27 | // } 28 | 29 | function runPublish(cb) { 30 | const PublishCommand = lerna.__commands__.publish; 31 | const flags = { 32 | skipNpm: true, 33 | skipGit: true, 34 | }; 35 | const publish = new PublishCommand([], flags); 36 | publish.runValidations(); 37 | publish.runPreparations(); 38 | 39 | publish.initialize(function (err) { 40 | if (err) throw err; 41 | 42 | publish.updateUpdatedPackages(); 43 | 44 | publish.updates.forEach(update => { 45 | const dir = update.package.location; 46 | const tag = 'latest'; 47 | npm.publishTaggedInDir(tag, dir, function (err, out) { 48 | cb(err, out, update, publish); 49 | }); 50 | }); 51 | }); 52 | } 53 | 54 | function run() { 55 | runPublish(function (err, out /*, update*/) { 56 | /* eslint-disable no-console */ 57 | // console.log(update.package.name, update.package.version); 58 | if (err) return console.error(err.toString()); 59 | console.log(out.toString()); 60 | /* eslint-enable */ 61 | }); 62 | } 63 | 64 | if (require.main === module) { 65 | run(); 66 | } 67 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-conditionals/test/index.js: -------------------------------------------------------------------------------- 1 | import conditionals from '../src'; 2 | 3 | function test(input) { 4 | return trim(transform(input, { 5 | plugins: [conditionals], 6 | babelrc: false 7 | }).code); 8 | } 9 | 10 | describe('babel-plugin-transform-conditionals', function () { 11 | it('should remove simple conditionals', function () { 12 | expect( 13 | test('if (false) { a() ;} else { b() }') 14 | ).toEqual( 15 | trim('b()') 16 | ); 17 | expect( 18 | test('if (true) { a() } else { b() }') 19 | ).toEqual( 20 | trim('a()') 21 | ) 22 | }); 23 | 24 | it('should preserve block for declarations', function () { 25 | expect( 26 | test('if (1) { let x = a; }') 27 | ).toEqual( 28 | trim('{let x = a;}') 29 | ); 30 | }); 31 | 32 | it('should preserve declarations in the removed part', function () { 33 | expect( 34 | test('if (1) { var a = 5 } else { var b = 6 }') 35 | ).toEqual( 36 | trim('var a = 5; var b;') 37 | ); 38 | expect( 39 | test('if (0) { var x = "blah" } else { var y = false; }') 40 | ).toEqual( 41 | trim('var x; var y = false') 42 | ); 43 | }); 44 | 45 | it('should treat non block statements the same way', function () { 46 | expect( 47 | test('if (1) var a = 5; else var b = 6;') 48 | ).toEqual( 49 | trim('var a = 5; var b;') 50 | ); 51 | }); 52 | 53 | it('should work with conditional operator', function () { 54 | expect(test('false ? a : b')).toEqual(trim('b')); 55 | expect(test('true ? a : b')).toEqual(trim('a')); 56 | }); 57 | 58 | it('should evaluate conditional.test', function () { 59 | expect( 60 | test('if (false && true || 0) { var x = 5; }') 61 | ).toEqual( 62 | trim('var x;') 63 | ); 64 | expect( 65 | test('if ((5 + 10) % 10 - 4 && "asdf".indexOf("a") > -1) { var x = 5; }') 66 | ).toEqual( 67 | trim('var x = 5;') 68 | ); 69 | }); 70 | 71 | it('should treat functions within block as strict mode Issue#14', function () { 72 | expect( 73 | test('if(false) {function a() {}} a();') 74 | ).toEqual( 75 | trim('a()') 76 | ); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-function-to-arrow/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /*::import type {NodePath, Binding, Scope, Node, PluginOptions} from 'Babel';*/ 3 | 4 | /** 5 | * For a particular path - Function Expression, 6 | * returns if that path is replacable to an arrow expression 7 | */ 8 | function isReplacable(path, keep_fnames) { 9 | if (keep_fnames && path.get('id').node) return false; 10 | let replacable = true; 11 | 12 | /** 13 | * If the function either uses this or arguments inside the function, 14 | * then it's NOT replacable 15 | */ 16 | path.traverse({ 17 | ThisExpression(thisPath) { 18 | if (thisPath.scope === path.scope) { 19 | replacable = false; 20 | } 21 | }, 22 | Identifier(idPath /*:NodePath*/) { 23 | if (idPath.node.name === 'arguments' && idPath.scope === path.scope) { 24 | replacable = false; 25 | } 26 | } 27 | }); 28 | return replacable; 29 | } 30 | 31 | /** 32 | * Find if the block of an arrow function can be removed 33 | * 34 | * We test if the arrow contains a single return statement 35 | * var a = () => { return x }; 36 | * var a = () => x; 37 | */ 38 | function isArrowReplacable(path) { 39 | let body = path.get('body'); 40 | if ( body.isBlockStatement() 41 | && body.node.body.length === 1 42 | && body.node.body[0].type === "ReturnStatement") 43 | return true; 44 | } 45 | 46 | export default function ({types: t} /*:PluginOptions*/) { 47 | return { 48 | visitor: { 49 | FunctionExpression(path /*:NodePath*/, { 50 | opts: { 51 | keep_fnames = false 52 | } = {} 53 | } /*:FunctionToArrowOptions*/ = {}) { 54 | if (isReplacable(path, keep_fnames)) { 55 | path.replaceWith( 56 | t.arrowFunctionExpression( 57 | path.node.params, path.node.body 58 | ) 59 | ); 60 | } 61 | }, 62 | ArrowFunctionExpression: { 63 | exit(path /*:NodePath*/) { 64 | if (isArrowReplacable(path)) { 65 | var body = path.get('body'); 66 | body.replaceWith( 67 | body.node.body[0].argument 68 | ); 69 | } 70 | } 71 | } 72 | } 73 | }; 74 | } 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are always welcome, no matter how large or small. Before contributing, please read the [code of conduct](CODE_OF_CONDUCT.md). 4 | 5 | + If you've found some bug, [file an issue](https://github.com/boopathi/babel-minify/issues/new). 6 | + If you've found some fix for a bug, either [file an issue](https://github.com/boopathi/babel-minify/issues/new) mentioning the bug and the fix or send a Pull Request. 7 | 8 | ## Requirements 9 | 10 | + Node 5 or greater 11 | 12 | ## Monorepo 13 | 14 | This project follows a monorepo approach similar to [Babel](https://github.com/babel/babel), and you can read more about it here - https://github.com/babel/babel/blob/master/doc/design/monorepo.md 15 | 16 | ## Install for dev 17 | 18 | Install all dev dependencies 19 | 20 | ```sh 21 | npm install 22 | ``` 23 | 24 | Bootstrap packages and links them. This project uses [lerna](https://github.com/lerna/lerna) and you can read more about what bootstrap means here - https://github.com/lerna/lerna#bootstrap 25 | 26 | ```sh 27 | npm run bootstrap 28 | ``` 29 | 30 | ## Build 31 | 32 | Builds all packages. Each package has a `src/` directory and gets transpiled for Node 5 into `lib/` directory. 33 | 34 | ```sh 35 | npm run build 36 | ``` 37 | 38 | To do incremental builds on file change, start the watch process, 39 | 40 | ```sh 41 | npm run watch 42 | ``` 43 | 44 | ## Lint - eslint 45 | 46 | ```sh 47 | npm run lint 48 | ``` 49 | 50 | + config for sources `packages/*/src/*.js` - [`.eslintrc`](.eslintrc) 51 | + config for tests `packages/*/test/*.js` - [`test.eslintrc`](utils/test.eslintrc) 52 | 53 | ## Type check - flow 54 | 55 | Flow annotations are used in comments (https://flowtype.org/blog/2015/02/20/Flow-Comments.html) 56 | 57 | ```sh 58 | npm run flow 59 | ``` 60 | 61 | Declarations for libraries are here in this directory - [declarations](declarations) 62 | 63 | ## Test 64 | 65 | ```sh 66 | # Runs test for all packages 67 | npm test 68 | ``` 69 | 70 | To test individual packages, use `TEST_GREP` env to provide a GREP string. All the tests are titled with their respective package names. So, to test just the package `babel-plugin-transform-mangle`, 71 | 72 | ```sh 73 | TEST_GREP=mangle npm test 74 | ``` 75 | -------------------------------------------------------------------------------- /packages/babel-minify/src/cli-yargs.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import yargs from 'yargs'; 3 | import D from './defaults'; 4 | /*::import type {Argv} from 'yargs'*/ 5 | 6 | function option(arg, name, desc) { 7 | let type; 8 | if (Array.isArray(D[name])) { 9 | type = 'array'; 10 | } else if (typeof D[name] === 'object') { 11 | return arg; 12 | } else { 13 | // This is to satisfy flow 14 | switch (typeof D[name]) { 15 | case 'string': type = 'string'; break; 16 | case 'number': type = 'number'; break; 17 | case 'boolean': type = 'boolean'; break; 18 | } 19 | } 20 | 21 | return arg.option(name, { 22 | type, 23 | describe: desc, 24 | 'default': D[name] 25 | }); 26 | } 27 | 28 | export default function yargsOptions(_argv /*:string[]*/) /*:Argv*/ { 29 | const argv = yargs 30 | .usage('$0 [options] input.js') 31 | .help('help') 32 | .alias('help', 'h') 33 | .version(() => require('../package.json').version) 34 | .alias('version', 'v') 35 | .option('output', { 36 | alias: 'o', type: 'string', nargs: 1, 37 | describe: 'Output File' 38 | }) 39 | .option('outputDir', { 40 | alias: 'd', type: 'string', nargs: 1, 41 | describe: 'Output Directory' 42 | }); 43 | 44 | option(argv, 'mangle', 'Mangle Identifiers'); 45 | option(argv, 'dead_code', 'Remove Dead Code'); 46 | option(argv, 'conditionals', 'Optimize conditionals statements and expressions'); 47 | option(argv, 'evaluate', 'Constant Folding and Propagation'); 48 | option(argv, 'drop_debugger', 'Remove debugger statements'); 49 | option(argv, 'drop_console', 'Remove console.* statements'); 50 | option(argv, 'properties', 'Fix/Optimize property names'); 51 | option(argv, 'join_vars', 'Join consecutive variable declarations'); 52 | option(argv, 'booleans', 'Optimize booleans'); 53 | option(argv, 'unsafe', 'undefined to void, simplify comparision, function to arrow'); 54 | option(argv, 'keep_fnames', 'Preserve function names from mangling'); 55 | option(argv, 'passes', 'Number of passes'); 56 | option(argv, 'babelrc', 'Should babelrc be used'); 57 | option(argv, 'presets', 'pass extra presets to babel transformation'); 58 | option(argv, 'plugins', 'pass extra plugins to babel transformation'); 59 | 60 | return argv.demand(1).parse(_argv); 61 | } 62 | -------------------------------------------------------------------------------- /utils/clean-empty-modules.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const rimraf = require('rimraf'); 4 | const exec = require('child_process').execSync; 5 | 6 | const root = path.join(__dirname, '..'); 7 | 8 | module.exports = cleanEmptyModules; 9 | 10 | if (require.main === module) { 11 | cleanEmptyModules(); 12 | } 13 | 14 | function cleanEmptyModules() { 15 | const emptyMods = getEmptyModules(); 16 | 17 | const gitStatus = exec('git status --porcelain', { 18 | cwd: root, 19 | timeout: 5000 20 | }); 21 | 22 | const untrackedMods = gitStatus.toString() 23 | .split(/\r?\n/) 24 | .map(line => line.trim()) 25 | .filter(line => line.indexOf('??') === 0) 26 | .map(line => line.split('??')[1].trim()) 27 | .filter(file => file.indexOf('packages') === 0) 28 | .map(file => path.resolve(path.join(root, file))) 29 | 30 | const toRemove = emptyMods 31 | .map(mod => path.resolve(path.join(root, 'packages', mod))) 32 | .filter(mod => untrackedMods.indexOf(mod) === -1); 33 | 34 | toRemove.forEach(mod => { 35 | rimraf.sync(mod); 36 | }); 37 | 38 | const warning = 'The following packages were not removed becuase there are some untracked files in it\n' 39 | + untrackedMods.join('\n'); 40 | 41 | return { 42 | removedAbs: toRemove, 43 | removed: toRemove.map(mod => path.relative(path.join(root, 'packages'), mod)), 44 | keptAbs: untrackedMods, 45 | kept: untrackedMods.map(mod => path.relative(path.join(root, 'packages'), mod)), 46 | warning 47 | }; 48 | } 49 | 50 | function getEmptyModules() { 51 | return fs 52 | .readdirSync(path.join(root, 'packages')) 53 | .filter(mod => isDir(path.join(root, 'packages', mod))) 54 | .filter(mod => { 55 | if (isFile(path.join(root, 'packages', mod, 'package.json'))) { 56 | // not empty 57 | return false; 58 | } else if (isFile(path.join(root, 'packages', mod, 'index.js'))) { 59 | // not empty 60 | return false; 61 | } 62 | // empty module 63 | return true; 64 | }); 65 | } 66 | 67 | function isFile(file) { 68 | try { 69 | return fs.statSync(file).isFile(); 70 | } catch (e) { 71 | return false; 72 | } 73 | } 74 | 75 | function isDir(dir) { 76 | try { 77 | return fs.statSync(dir).isDirectory(); 78 | } catch (e) { 79 | return false; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-function-to-arrow/test/index.js: -------------------------------------------------------------------------------- 1 | import functionToArrow from '../src'; 2 | 3 | function test(input) { 4 | return trim(transform(input, { 5 | plugins: [functionToArrow], 6 | babelrc: false 7 | }).code); 8 | } 9 | 10 | function testOpts(input) { 11 | return trim(transform(input, { 12 | plugins: [[functionToArrow, {keep_fnames: true}]], 13 | babelrc: false 14 | }).code); 15 | } 16 | 17 | describe('babel-plugin-transform-function-to-arrow', function () { 18 | it('should transform var x = function expression', function () { 19 | expect( 20 | test('var x = function () {}') 21 | ).toEqual( 22 | trim('var x = () => {}') 23 | ); 24 | }); 25 | 26 | it('should transform single return arrows', function () { 27 | expect( 28 | test('var x = () => { return 5 }') 29 | ).toEqual( 30 | trim('var x = () => 5') 31 | ); 32 | }); 33 | 34 | it('should transform single return functions', function () { 35 | expect( 36 | test('var y = function (a) { return doSomething(a) + a; }') 37 | ).toEqual( 38 | trim('var y = a => doSomething(a) + a') 39 | ); 40 | }); 41 | 42 | it('should not transform functions with this and arguments', function () { 43 | expect( 44 | test('var x = function () { return arguments[0]; }') 45 | ).toEqual( 46 | trim('var x = function () { return arguments[0]; }') 47 | ); 48 | expect( 49 | test('const a = function (x) { this.x = x }') 50 | ).toEqual( 51 | trim('const a = function (x) { this.x = x }') 52 | ); 53 | }); 54 | 55 | it('should transform named functions by default (keep_fnames=false)', function () { 56 | expect( 57 | test('var x = function x() { return 5 }') 58 | ).toEqual( 59 | trim('var x = () => 5') 60 | ); 61 | }); 62 | 63 | it('should NOT transform named functions for keep_fnames=true', function () { 64 | expect( 65 | testOpts('var x = function x() { return 5 }') 66 | ).toEqual( 67 | trim('var x = function x() { return 5 }') 68 | ); 69 | }); 70 | 71 | it('should transform function where nested functions include this or arguments', function () { 72 | expect( 73 | test('var x = function () { var a = function () {this} }') 74 | ).toEqual( 75 | trim('var x = () => { var a = function() {this} }') 76 | ); 77 | expect( 78 | test('var x = function () { var a = function () {arguments} }') 79 | ).toEqual( 80 | trim('var x = () => { var a = function() {arguments} }') 81 | ); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-conditionals/src/extract-vars.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /*::import type {NodePath, Binding, Scope, Node, PluginOptions, Types} from 'Babel';*/ 3 | 4 | /** 5 | * We get all the variable declarations in the same scope, 6 | * and we take only the declartion WITHOUT the value outside 7 | * the block statement 8 | */ 9 | export default function extractVars(blockPath /*:NodePath*/, t /*:Types*/) /*:Node|null*/ { 10 | const declarators = []; 11 | 12 | if (blockPath.node === null) return null; 13 | 14 | /** 15 | * TODO: 16 | * Warn about constantViolations 17 | */ 18 | 19 | extractDeclarators(blockPath).forEach(decl => { 20 | /** 21 | * Get the binding identifiers 22 | */ 23 | const bindingIdentifiers = decl.getBindingIdentifiers(); 24 | Object.keys(bindingIdentifiers).forEach(id => { 25 | let alreadyFound = false; 26 | 27 | /** 28 | * Eliminate re-declarations inside the block that'll be removed 29 | * 30 | * var x; 31 | * if (0) var x; 32 | */ 33 | declarators.forEach(dl => { 34 | if (dl.id.name === id) { 35 | alreadyFound = true; 36 | } 37 | }); 38 | if (!alreadyFound) { 39 | declarators.push(t.variableDeclarator(bindingIdentifiers[id])); 40 | } 41 | }); 42 | }); 43 | 44 | if (declarators.length <= 0) return null; 45 | 46 | /** 47 | * return whatever we have collected wrapped up in 48 | * a single variable declaration 49 | */ 50 | return t.variableDeclaration('var', declarators); 51 | } 52 | 53 | /** 54 | * Get all Variable Declarations of kind 'var' 55 | * with the same function scope 56 | */ 57 | export function extractDeclarators(blockPath /*:NodePath*/) /*:NodePath[]*/ { 58 | const declarations = []; 59 | 60 | if (blockPath.isBlockStatement()) { 61 | /** 62 | * if (a) { 63 | * var x; // capture this 64 | * // functions are block scope 65 | * // https://github.com/boopathi/babel-minify/issues/14 66 | * function fn() { 67 | * var x, y; // don't capture this 68 | * } 69 | * } 70 | */ 71 | blockPath.traverse({ 72 | VariableDeclaration(varPath) { 73 | if (varPath.isVariableDeclaration({ kind:'var' }) 74 | && varPath.scope.getFunctionParent() === blockPath.scope.getFunctionParent()) { 75 | declarations.push(varPath); 76 | } 77 | } 78 | }); 79 | } else if (blockPath.isVariableDeclaration({ kind: 'var' })) { 80 | /** 81 | * if (a) var x; 82 | */ 83 | declarations.push(blockPath); 84 | } 85 | return declarations; 86 | } 87 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const babel = require('gulp-babel'); 3 | const through2 = require('through2'); 4 | const watch = require('gulp-watch'); 5 | const newer = require('gulp-newer'); 6 | const gutil = require('gulp-util'); 7 | const rimraf = require('rimraf'); 8 | const plumber = require('gulp-plumber'); 9 | const cleanEmptyModules = require('./utils/clean-empty-modules'); 10 | 11 | // for compilation 12 | const source = './packages/*/src/**/*.js'; 13 | const dest = './packages'; 14 | const srcEx = new RegExp("(packages/[^/]+)/src/"); 15 | const libFragment = "$1/lib/"; 16 | 17 | // for cleaning 18 | const libs = './packages/*/lib'; 19 | 20 | gulp.task('default', ['build']); 21 | 22 | gulp.task('clean', function(cb) { 23 | rimraf.sync(libs, { glob: true }); 24 | 25 | const out = cleanEmptyModules(); 26 | 27 | if (out.removed.length > 0) 28 | gutil.log(gutil.colors.cyan('Empty Packages - REMOVED')); 29 | out.removed.forEach(mod => { 30 | gutil.log(gutil.colors.red(mod)); 31 | }); 32 | 33 | if (out.kept.length > 0) 34 | gutil.log(gutil.colors.cyan('Unstaged Empty Packages - NOT REMOVED')); 35 | out.kept.forEach(mod => { 36 | gutil.log(gutil.colors.yellow(mod)); 37 | }); 38 | 39 | cb(); 40 | }); 41 | 42 | gulp.task('build', function() { 43 | return gulp.src(source) 44 | .pipe(plumber()) 45 | .pipe(through2.obj(function(file, enc, callback) { 46 | file._path = file.path; 47 | file.path = file.path.replace(srcEx, libFragment); 48 | callback(null, file); 49 | })) 50 | .pipe(newer(dest)) 51 | .pipe(through2.obj(function(file, env, callback) { 52 | gutil.log('Compiling', gutil.colors.cyan(file._path)); 53 | callback(null, file); 54 | })) 55 | .pipe(babel()) 56 | .pipe(gulp.dest(dest)); 57 | }); 58 | 59 | /* eslint-disable no-unused-vars */ 60 | gulp.task('watch', ['build'] ,function(callback) { 61 | watch(source, function() { 62 | gulp.start('build'); 63 | }); 64 | }); 65 | /* eslint-enable */ 66 | 67 | 68 | /** 69 | * Build for browser 70 | */ 71 | const browserify = require('browserify'); 72 | const babelify = require('babelify'); 73 | const vinylSource = require('vinyl-source-stream'); 74 | const vinylBuffer = require('vinyl-buffer'); 75 | const uglify = require('gulp-uglify'); 76 | const copy = require('gulp-copy'); 77 | 78 | gulp.task('browser', function() { 79 | const bundler = browserify('./utils/browser.js', { 80 | standalone: 'BabelMinify' 81 | }).transform(babelify, { 82 | presets: ['es2015'] 83 | }); 84 | 85 | return bundler.bundle() 86 | .pipe(vinylSource('babel-minify.js')) 87 | .pipe(vinylBuffer()) 88 | .pipe(uglify()) 89 | .pipe(gulp.dest('./build')); 90 | }); 91 | 92 | gulp.task('gh-pages', ['browser'], function() { 93 | return gulp.src('build/*') 94 | .pipe(copy('../babel-minify-gh-pages/build', { 95 | prefix: 1 96 | })); 97 | }); 98 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-conditionals/src/replace-with.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /*::import type {NodePath, Binding, Scope, Node, PluginOptions, Types} from 'Babel';*/ 3 | import extractVars from './extract-vars'; 4 | 5 | export default function replaceWith(to /*:string*/, path /*:NodePath*/, t /*:Types*/) { 6 | /** 7 | * The block that is to be removed 8 | */ 9 | const remove = to === 'consequent' ? 'alternate' : 'consequent'; 10 | const replacements /*:Node[]*/ = []; 11 | 12 | const toBlock /*:NodePath*/ = path.get(to); 13 | const removeBlock /*:NodePath*/ = path.get(remove); 14 | 15 | if (toBlock.isBlockStatement()) { 16 | /** 17 | * if (0) { 18 | * // removeBlock 19 | * } else { 20 | * // toBlock - BlockStatement 21 | * } 22 | */ 23 | if (Object.keys(toBlock.scope.bindings).length > 0) { 24 | /** 25 | * If the success block contains some bindings on its own 26 | * using let or const, then keep the block as it is 27 | * 28 | * if (1) { 29 | * let x = 1; 30 | * } 31 | * out => 32 | * { 33 | * let x = 1; 34 | * } 35 | */ 36 | replacements.push(toBlock.node); 37 | } else { 38 | /** 39 | * Else just put every statement in the replacement, and 40 | * the placing them within a block is unnecessary 41 | * 42 | * if (1) { 43 | * doThis(); 44 | * doThat(); 45 | * } 46 | * out => 47 | * doThis(); 48 | * doThat(); 49 | */ 50 | toBlock.node.body.forEach(e => replacements.push(e)); 51 | } 52 | } else if (toBlock.isVariableDeclaration() || toBlock.node !== null) { 53 | /** 54 | * If it's either a variable declaration or it's just some statement, 55 | * there is no need for CREATING a new block statement, just pass it along 56 | * 57 | * in => if (1) var x = 5; 58 | * out => var x = 5; 59 | * in => if (1) doSomething(); 60 | * out => doSomething(); 61 | */ 62 | replacements.push(toBlock.node); 63 | } 64 | 65 | /** 66 | * Capture other declarations. 67 | * Check the definition of dontForgetAlternate 68 | * 69 | * in => if (0) var x = blahBlah(); 70 | * out => var x; 71 | */ 72 | const decl = extractVars(removeBlock, t); 73 | if (decl) { 74 | /** 75 | * Here we decide whether to push it to the front or back 76 | * 77 | * in => if (0) { var x = 5 } else { var y = 10 } 78 | * out => var x; var y = 10; 79 | * 80 | * in => if (1) { var x = 5 } else { var y = 10 } 81 | * out => var x = 5; var y; 82 | */ 83 | if (to === 'alternate') { 84 | replacements.unshift(decl); 85 | } else { 86 | replacements.push(decl); 87 | } 88 | } 89 | 90 | /** 91 | * And finally, replace the conditional with whatever we've collected 92 | */ 93 | path.replaceWithMultiple(replacements); 94 | } 95 | -------------------------------------------------------------------------------- /packages/babel-minify/src/cli-runner.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /*::import type {Argv} from 'yargs'*/ 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | import mkdirp from 'mkdirp'; 6 | import minify from './'; 7 | 8 | function handleFile(filename /*:string*/, options /*:MinifierOptions*/) /*:CliTaskResult*/ { 9 | const input = String(fs.readFileSync(filename)); 10 | const contents = minify(input, options).toString(); 11 | return { contents, filename }; 12 | } 13 | 14 | function putFile( 15 | result /*:CliTaskResult*/, 16 | argv /*:Argv*/, 17 | opts /*:CliRunnerOptions*/ 18 | /*inputFile /*:string*/ 19 | ) { 20 | if (typeof argv.output === 'string') { 21 | if (argv._.length === 1) { 22 | fs.writeFileSync(argv.output, result.contents, 'utf-8'); 23 | } else { 24 | opts.logger.error('--outputDir unspecified'); 25 | throw new Error('--outputDir unspecified'); 26 | } 27 | } else if (typeof argv.outputDir === 'string') { 28 | const {outputDir} = argv; 29 | const basename = path.basename(result.filename); 30 | fs.writeFileSync(path.join(outputDir, basename), result.contents, 'utf-8'); 31 | } else { 32 | opts.logger.log(result.contents); 33 | } 34 | } 35 | 36 | function walk(dir /*:string*/) /*:string[]*/ { 37 | let results = []; 38 | const list = fs.readdirSync(dir); 39 | list.forEach(_file => { 40 | const file = path.join(dir, _file); 41 | if (fs.statSync(file).isDirectory()) 42 | results = results.concat(walk(file)); 43 | else 44 | results.push(file); 45 | }); 46 | return results; 47 | } 48 | 49 | function handleDir(dir /*:string*/, options /*:MinifierOptions*/) /*:CliTaskResult[]*/ { 50 | let files = walk(dir); 51 | return files.map(filename => handleFile(filename, options)); 52 | } 53 | 54 | function putDir( 55 | results /*:CliTaskResult[]*/, 56 | argv /*:Argv*/, 57 | opts /*:CliRunnerOptions*/, 58 | inputDir /*:string*/ 59 | ) { 60 | const outputDir /*:string*/ = typeof argv.outputDir === 'string' ? argv.outputDir : ''; 61 | if (outputDir) { 62 | results.forEach(result => { 63 | const inputfile = path.resolve(path.join('./', result.filename)); 64 | const inputDirAbs = path.resolve(path.join('./', inputDir)); 65 | const relativePath = inputfile.replace(inputDirAbs, ''); 66 | 67 | const outfile = path.join(outputDir, relativePath); 68 | const dirname = path.dirname(outfile); 69 | 70 | mkdirp.sync(dirname); 71 | fs.writeFileSync(outfile, result.contents, 'utf-8'); 72 | }); 73 | } else { 74 | opts.logger.error('--outputDir unspecified'); 75 | throw new Error('--outputDir unspecified'); 76 | } 77 | } 78 | 79 | export default function minifyAndOutput(argv /*:Argv*/, opts /*:CliRunnerOptions*/) { 80 | argv._ 81 | .forEach(file => { 82 | if (fs.statSync(file).isDirectory()) { 83 | let results = handleDir(file, argv); 84 | putDir(results, argv, opts, file); 85 | } else { 86 | let result = handleFile(file, argv); 87 | putFile(result, argv, opts, file); 88 | } 89 | }); 90 | } 91 | -------------------------------------------------------------------------------- /declarations/babel.js: -------------------------------------------------------------------------------- 1 | declare module Babel { 2 | declare type VariableKind = 'var' | 'let' | 'const' 3 | 4 | declare type Declarator = Node; 5 | 6 | declare class Types { 7 | valueToNode(a :any): Node, 8 | variableDeclaration(v: VariableKind, d: Declarator[]): Node, 9 | variableDeclarator(n: Node): Node, 10 | arrowFunctionExpression(params: any, body: any): Node, 11 | } 12 | 13 | declare type PluginOptions = { 14 | types: Types 15 | } 16 | 17 | declare function PluginFunction(t: PluginOptions): Object 18 | 19 | declare type Plugin = string | Array | PluginFunction 20 | 21 | declare type Preset = { 22 | plugins: Plugin[] 23 | } 24 | 25 | declare class Binding { 26 | scope: Scope, 27 | path: NodePath, 28 | deoptValue(): void, 29 | setValue(v: any): void, 30 | hasDeoptedValue: bool, 31 | } 32 | 33 | declare class Node { 34 | type: string, 35 | body: Node[], 36 | name: string, 37 | params: any, 38 | argument: Node, 39 | id: Object, 40 | } 41 | 42 | declare class NodePath { 43 | isIdentifier(): bool, 44 | 45 | isVariableDeclaration(): bool, 46 | 47 | isBlockStatement(): bool, 48 | 49 | isMemberExpression(): bool, 50 | 51 | isFunctionDeclaration(): bool, 52 | isFunctionExpression(): bool, 53 | isClassDeclaration(): bool, 54 | isClassExpression(): bool, 55 | 56 | scope: Scope, 57 | node: Node, 58 | parentPath: NodePath, 59 | 60 | get(s: string): NodePath, 61 | replaceWith(n: Node): void, 62 | replaceWithMultiple(n: Node[]): void, 63 | skip(): void, 64 | traverse(v: Object): void, 65 | evaluate(): EvaluateResult, 66 | getBindingIdentifiers(): Object, 67 | matchesPattern(a :string): bool, 68 | 69 | // others 70 | forEach(a: any): void, 71 | 72 | getSource(): string, 73 | 74 | // FIXME 75 | // Temporary Fix 76 | [key: string]: Function, 77 | } 78 | 79 | declare class Scope { 80 | getFunctionParent(): Scope, 81 | getProgramParent(): Scope, 82 | getBinding(b: string): Binding | null, 83 | rename(a: string, b: string): void, 84 | hasOwnBinding(a: string): bool, 85 | getAllBindings(): Object, 86 | hasReference(a: string): bool, 87 | hasGlobal(a: string): bool, 88 | hasBinding(a: string): bool, 89 | bindings: Object, 90 | } 91 | 92 | declare class EvaluateResult { 93 | confident: bool, 94 | deopt: NodePath[], 95 | value: any 96 | } 97 | } 98 | 99 | declare module 'babel-core' { 100 | declare type BabelOptions = { 101 | plugins: Plugin[], 102 | presets: Preset[], 103 | minified: bool, 104 | compact: bool, 105 | passPerPreset: bool, 106 | comments: bool, 107 | babelrc?: bool, 108 | sourceMaps?: bool, 109 | inputSourceMaps?: Object | null, 110 | extends?: string, 111 | env?: Object, 112 | } 113 | 114 | declare type BabelResult = { 115 | code: string, 116 | map?: Object | null, 117 | toString() :string, 118 | } 119 | 120 | declare function transform(input: string, options: BabelOptions): BabelResult 121 | } 122 | -------------------------------------------------------------------------------- /packages/babel-minify/test/index.js: -------------------------------------------------------------------------------- 1 | import minify from '../'; 2 | 3 | describe('babel-minify', function() { 4 | it('should throw when input is null', function() { 5 | const inputs = [null, false, void 0]; 6 | inputs.forEach(input => { 7 | expect(() => minify(input)).toThrow(); 8 | }); 9 | }); 10 | 11 | it('should NOT throw when input is null and minify is false', function() { 12 | const inputs = [null, false, void 0]; 13 | inputs.forEach(input => { 14 | expect(() => minify(input, {minify: false})).toNotThrow(); 15 | }); 16 | }); 17 | 18 | describe('minify-options', function() { 19 | const optionsPluginsMap = { 20 | mangle: ['babel-plugin-transform-mangle'], 21 | dead_code: ['babel-plugin-transform-dead-code-elimination'], 22 | conditionals: ['babel-plugin-transform-conditionals'], 23 | evaluate: ['babel-plugin-transform-evaluate'], 24 | drop_debugger: ['babel-plugin-transform-remove-debugger'], 25 | drop_console: ['babel-plugin-transform-remove-console'], 26 | properties: [ 27 | 'babel-plugin-transform-member-expression-literals', 28 | 'babel-plugin-transform-property-literals' 29 | ], 30 | join_vars: ['babel-plugin-transform-merge-sibling-variables'], 31 | booleans: ['babel-plugin-transform-minify-booleans'], 32 | unsafe: [ 33 | 'babel-plugin-transform-undefined-to-void', 34 | 'babel-plugin-transform-simplify-comparison-operators', 35 | 'babel-plugin-transform-function-to-arrow' 36 | ] 37 | }; 38 | 39 | function flattenPlugin(p) { 40 | if (Array.isArray(p)) return p[0]; 41 | return p; 42 | } 43 | function getPlugin(name) { 44 | let plugin = require(name); 45 | if (plugin.__esModule) return plugin.default; 46 | return plugin; 47 | } 48 | function getActualPlugins(option, enabled) { 49 | const actualPluginsAndPresets = minify(null, { 50 | minify: false, 51 | [option]: enabled 52 | }); 53 | const plugins = actualPluginsAndPresets.plugins.map(p => flattenPlugin(p)); 54 | 55 | actualPluginsAndPresets.presets.forEach(preset => { 56 | preset.plugins.map(p => flattenPlugin(p)).forEach(plugin => { 57 | plugins.push(plugin); 58 | }); 59 | }); 60 | return plugins; 61 | } 62 | 63 | Object.keys(optionsPluginsMap).map(option => { 64 | it (`should enable/disable corresponding plugins for option = ${option}`, function() { 65 | const expected = optionsPluginsMap[option].map(name => getPlugin(name)); 66 | 67 | const actualEnabled = getActualPlugins(option, true); 68 | 69 | const actualDisabled = getActualPlugins(option, false); 70 | 71 | expected.forEach(e => { 72 | expect(actualEnabled).toInclude(e); 73 | expect(actualDisabled).toExclude(e); 74 | }); 75 | }); 76 | }); 77 | }); 78 | 79 | it('should return a babel result', function () { 80 | const result = minify('function a() { var foo; }'); 81 | expect(result.code).toBeA('string'); 82 | expect(result.toString()).toEqual(result.code); 83 | }); 84 | 85 | it('should contain sourcemaps', function () { 86 | const result = minify('function a() { var foo; }', {sourceMaps: true}); 87 | expect(result.code).toBeA('string'); 88 | expect(result.map).toBeAn('object'); 89 | expect(result.map.mappings).toBeA('string'); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at me@boopathi.in. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /packages/babel-minify/test/cli.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | import rimraf from 'rimraf'; 4 | import mkdirp from 'mkdirp'; 5 | import cli from '../src/cli'; 6 | 7 | function clean() { 8 | rimraf.sync(path.join(__dirname, 'resources', 'lib')); 9 | } 10 | 11 | clean(); 12 | 13 | const event = { 14 | callbacks: [], 15 | trigger(type, ...result) { 16 | this.callbacks.forEach(fn => fn(type, ...result)); 17 | }, 18 | subscribe(fn) { 19 | this.callbacks.push(fn); 20 | }, 21 | clear() { 22 | this.callbacks = []; 23 | } 24 | }; 25 | 26 | const logger = { 27 | log(...args) { event.trigger('log', ...args) }, 28 | warn(...args) { event.trigger('warn', ...args) }, 29 | error(...args) { event.trigger('error', ...args) } 30 | }; 31 | 32 | describe('babel-minify-cli', function () { 33 | it('should throw when no file is passed', function () { 34 | expect( 35 | () => { cli(['notfound']) } 36 | ).toThrow(); 37 | }); 38 | 39 | beforeEach(function () { 40 | event.clear(); 41 | mkdirp.sync('packages/babel-minify/test/resources/lib'); 42 | }); 43 | 44 | afterEach(function () { 45 | clean(); 46 | }); 47 | 48 | it('should change file and display output', function () { 49 | event.subscribe((type, output) => { 50 | expect(type).toEqual('log'); 51 | expect( 52 | trim(output) 53 | ).toEqual( 54 | trim('(()=>{let a = 1, b = 2; window.baz = 3})()') 55 | ) 56 | }); 57 | cli(['packages/babel-minify/test/resources/src/bar.js'], { logger }); 58 | }); 59 | 60 | it('should work for a single file with output dir', function () { 61 | event.subscribe(type => { 62 | expect(type).toEqual('log'); 63 | const outfile = path.join(__dirname, 'resources', 'lib', 'bar.js'); 64 | expect(() => fs.statSync(outfile)).toNotThrow(); 65 | expect(fs.statSync(outfile).isFile()).toEqual(true); 66 | }); 67 | 68 | cli([ 69 | '--outputDir', 70 | 'packages/babel-minify/test/resources/lib', 71 | 'packages/babel-minify/test/resources/src/bar.js' 72 | ], { logger }); 73 | }); 74 | 75 | it('should work for a single file with output file specified', function () { 76 | event.subscribe(type => { 77 | expect(type).toEqual('log'); 78 | const outfile = path.join(__dirname, 'resources', 'lib', 'barbar.js'); 79 | expect(() => fs.statSync(outfile)).toNotThrow(); 80 | expect(fs.statSync(outfile).isFile()).toEqual(true); 81 | }); 82 | 83 | cli([ 84 | '--output', 85 | 'packages/babel-minify/test/resources/lib/barbar.js', 86 | 'packages/babel-minify/test/resources/src/bar.js' 87 | ], { logger }); 88 | }); 89 | 90 | it('should throw when input is dir and outputDir is null', function () { 91 | event.subscribe(type => { 92 | expect(type).toEqual('error'); 93 | }); 94 | 95 | expect( 96 | () => cli([ 97 | 'packages/babel-minify/test/resources/src' 98 | ], { logger }) 99 | ).toThrow(); 100 | }); 101 | 102 | it('should work for input dir and output dir', function () { 103 | cli([ 104 | '--output-dir', 105 | 'packages/babel-minify/test/resources/lib', 106 | 'packages/babel-minify/test/resources/src' 107 | ]); 108 | 109 | ['foo/a.js', 'foo/b.js', 'foo/c.js', 'bar.js', 'baz.js'] 110 | .map(p => path.join('packages/babel-minify/test/resources/lib', p)) 111 | .forEach(p => { 112 | fs.statSync(p); 113 | expect(() => fs.statSync(p)).toNotThrow(); 114 | }); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /packages/babel-minify/README.md: -------------------------------------------------------------------------------- 1 | # babel-minify 2 | 3 | Node API + CLI 4 | 5 | ## Install 6 | 7 | ``` 8 | npm install babel-minify --save-dev 9 | ``` 10 | 11 | ## Example 12 | 13 | ```js 14 | import minify from 'babel-minify'; 15 | const outputCode = minify(inputCode, { 16 | conditionals: true, 17 | drop_debugger: true 18 | }); 19 | ``` 20 | 21 | ## Options 22 | 23 | Options and defaults 24 | 25 | ```js 26 | { 27 | mangle = true, 28 | mangle_globals = false, 29 | 30 | dead_code = false, 31 | conditionals = true, 32 | evaluate = true, // eval constant expressions 33 | drop_debugger = false, 34 | drop_console = false, 35 | properties = true, 36 | join_vars = true, 37 | booleans = true, 38 | unsafe = true, 39 | keep_fnames = false, 40 | 41 | // global_defs 42 | global_defs = {}, 43 | 44 | // number of passes 45 | passes = 1, 46 | 47 | // passed on to babel transform to tell whether to use babelrc 48 | babelrc = false, 49 | 50 | // should there be any other plugins added to this build process 51 | plugins = [], 52 | 53 | // should there be any other presets 54 | presets = [], 55 | 56 | // if false, babel-minify can give a list of plugins to use as a preset 57 | minify = true, 58 | } 59 | ``` 60 | 61 | ## Internals 62 | 63 | Plugins used for specific options 64 | 65 | + [mangle](https://github.com/boopathi/babel-minify/tree/master/packages/babel-plugin-transform-mangle) 66 | + [mangle-options](https://github.com/boopathi/babel-minify/tree/master/packages/babel-plugin-transform-mangle#options) - Pass in a boolean to enable(defaults)/disable or pass in an object with these options 67 | + `keep_fnames`: [Default: false] Don't mangle function names for FunctionExpressions and FunctionDeclarations - Useful for code depending on `fn.name`. Note - This overrides keep_fnames in the global options (look below) 68 | + `topLevel`: [Default: false] Mangle variables in the outermost scope 69 | + `eval`: [Default: false] Don't deopt from mangling when eval is found in the subtree 70 | + `except`: [Default: []] Pass in an array of strings or functions or RegExps to match against the variable name that is mangled. If there is a match, then that variable name will NOT be mangled. 71 | + [dead_code](https://www.npmjs.com/package/babel-plugin-transform-dead-code-elimination) 72 | + [conditionals](https://github.com/boopathi/babel-minify/tree/master/packages/babel-plugin-transform-conditionals) 73 | + [global_defs](https://github.com/boopathi/babel-minify/tree/master/packages/babel-plugin-transform-global-defs) 74 | + [evaluate](https://github.com/boopathi/babel-minify/tree/master/packages/babel-plugin-transform-evaluate) 75 | + [drop_debugger](https://www.npmjs.com/package/babel-plugin-transform-remove-debugger) 76 | + [drop_console](https://www.npmjs.com/package/babel-plugin-transform-remove-console) 77 | + properties - [member-expression-literals](https://www.npmjs.com/package/babel-plugin-transform-member-expression-literals), [property-literals](https://www.npmjs.com/package/babel-plugin-transform-property-literals) 78 | + [join_vars](https://www.npmjs.com/package/babel-plugin-transform-merge-sibling-variables) 79 | + booleans - [minify booleans](https://www.npmjs.com/package/babel-plugin-transform-minify-booleans) 80 | + unsafe - [undefined-to-void](https://www.npmjs.com/package/babel-plugin-transform-undefined-to-void), [simplify-comparison-operators](https://www.npmjs.com/package/babel-plugin-transform-simplify-comparison-operators), [function-to-arrow](https://github.com/boopathi/babel-minify/tree/master/packages/babel-plugin-transform-function-to-arrow) 81 | + keep_fnames - [mangle-options](https://github.com/boopathi/babel-minify/tree/master/packages/babel-plugin-transform-mangle#options), [function-to-arrow-options](https://github.com/boopathi/babel-minify/tree/master/packages/babel-plugin-transform-function-to-arrow#options) 82 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-evaluate/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /*::import type {NodePath, Binding, Scope, Node, PluginOptions} from 'Babel';*/ 3 | export default function Evaluate({types: t} /*:PluginOptions*/) { 4 | return { 5 | visitor: { 6 | 'BinaryExpression|LogicalExpression': { 7 | exit(path /*:NodePath*/) { 8 | /** 9 | * Tells whether a binding is deopted 10 | * var x = 5; if (a) { var x = 6 } 11 | * var x = 1; if (b) { x = 0 } 12 | */ 13 | function isDeopt(id) { 14 | if (!id.isIdentifier()) return false; 15 | const binding = path.scope.getBinding(id.node.name); 16 | return !binding || binding.hasDeoptedValue; 17 | } 18 | 19 | if (isDeopt(path.get('left'))) return; 20 | if (isDeopt(path.get('right'))) return; 21 | 22 | const evaluated = path.evaluate(); 23 | if (evaluated.confident && evaluated.value.toString().length <= path.getSource().length) { 24 | path.replaceWith(t.valueToNode(evaluated.value)); 25 | } 26 | } 27 | }, 28 | 29 | CallExpression: { 30 | exit(path /*:NodePath*/) { 31 | const evaluated = path.evaluate(); 32 | if (evaluated.confident) path.replaceWith(t.valueToNode(evaluated.value)); 33 | } 34 | }, 35 | 36 | VariableDeclaration: { 37 | enter(path /*:NodePath*/) { 38 | /** 39 | * We are using this hook to deopt variable re-declarations 40 | * 41 | * Babel (<6.10) has a bug that it doesn't deopt variable re-declarations, 42 | * so, we do that here. 43 | * 44 | * https://github.com/babel/babel/pull/3559 45 | * https://phabricator.babeljs.io/T7470 46 | */ 47 | path.get('declarations').forEach(decl => { 48 | const init = decl.get('init'); 49 | const idBindings = decl.getBindingIdentifiers(); 50 | 51 | function deopt () { 52 | Object.keys(idBindings).forEach(id => { 53 | const binding = path.scope.getBinding(id); 54 | if (binding) binding.deoptValue(); 55 | }); 56 | } 57 | 58 | if (init) { 59 | if (init.isIdentifier()) { 60 | /** 61 | * var x = a; // a is a deopted value, deopt x 62 | */ 63 | const binding = path.scope.getBinding(init.node.name); 64 | if (!binding || binding.hasDeoptedValue) { 65 | deopt() 66 | } 67 | } else { 68 | /** 69 | * var x = a + b; // b is a deopted value, deopt x 70 | */ 71 | init.traverse({ 72 | Identifier(idPath) { 73 | const binding = path.scope.getBinding(idPath.node.name); 74 | if (!binding || binding.hasDeoptedValue) { 75 | deopt(); 76 | } 77 | } 78 | }); 79 | } 80 | } 81 | }); 82 | }, 83 | exit(path /*:NodePath*/) { 84 | /** 85 | * This space is for evaluating the right side of the 86 | * variable declaration 87 | */ 88 | path.get('declarations').forEach(decl => { 89 | const init = decl.get('init'); 90 | if (init && init.isIdentifier()) { 91 | const binding = path.scope.getBinding(init.node.name); 92 | if (!binding || binding.hasDeoptedValue) return; 93 | const evaluated = init.evaluate(); 94 | if (evaluated.confident) { 95 | init.replaceWith(t.valueToNode(evaluated.value)); 96 | } 97 | } 98 | }); 99 | } 100 | } 101 | } 102 | }; 103 | } 104 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-global-defs/test/index.js: -------------------------------------------------------------------------------- 1 | import globalDefs from '../src'; 2 | 3 | function test(input, definitions) { 4 | return trim(transform(input, { 5 | plugins: [[globalDefs, { 6 | global_defs: definitions 7 | }]], 8 | babelrc: false 9 | }).code); 10 | } 11 | 12 | describe('babel-plugin-transform-global-defs', function () { 13 | it('should transform simple variables', function () { 14 | expect( 15 | test('x', {x: false}) 16 | ).toEqual( 17 | trim('false') 18 | ); 19 | // should stringify 20 | expect( 21 | test('x', {x: 'helloworld'}) 22 | ).toEqual( 23 | trim('"helloworld"') 24 | ); 25 | expect( 26 | test('x', {x: 5}) 27 | ).toEqual( 28 | trim('5') 29 | ); 30 | }); 31 | 32 | it('should deopt when a global variable is re assigned', function () { 33 | expect( 34 | test('x = 5; x', {x:0}) 35 | ).toEqual( 36 | trim('x = 5; x') 37 | ); 38 | }); 39 | 40 | it('should transform object member expressions', function () { 41 | expect( 42 | test('process.env.DEBUG', {process: {env: {DEBUG: false}}}) 43 | ).toEqual( 44 | trim('false') 45 | ); 46 | }); 47 | 48 | it('should deopt object member expression when reassigned', function () { 49 | expect( 50 | test( 51 | 'process.env.NODE_ENV = "test"; process.env.NODE_ENV', 52 | {process: {env: {NODE_ENV: 'production'}}} 53 | ) 54 | ).toEqual( 55 | trim('process.env.NODE_ENV = "test"; process.env.NODE_ENV') 56 | ); 57 | }); 58 | 59 | it('should deopt object member expression when subpath is reassigned', function () { 60 | expect( 61 | test( 62 | 'process.env = "test"; process.env.NODE_ENV', 63 | {process: {env: {NODE_ENV: 'production'}}} 64 | ) 65 | ).toEqual( 66 | trim('process.env = "test"; process.env.NODE_ENV') 67 | ); 68 | }); 69 | 70 | it('should NOT deopt object member expression when a different subpath is changed', function () { 71 | expect( 72 | test( 73 | 'process.env.somethingelse = 5; process.env.NODE_ENV', 74 | {process: {env: {NODE_ENV: 'production'}}} 75 | ) 76 | ).toEqual( 77 | trim('process.env.somethingelse = 5; "production"') 78 | ); 79 | }); 80 | 81 | it('should NOT deopt when a same name local variable is changed', function () { 82 | expect( 83 | test( 84 | 'if (1) { const x = "bar" }; x;', 85 | {x: 'foo'} 86 | ) 87 | ).toEqual( 88 | trim('if (1) { const x = "bar" }; "foo";') 89 | ); 90 | }); 91 | 92 | it('should NOT deopt object member expression for a local variable change', function () { 93 | expect( 94 | test( 95 | 'if (1) { const process = {} }; process.env.DEBUG', 96 | {process: {env: {DEBUG: false}}} 97 | ) 98 | ).toEqual( 99 | trim('if (1) { const process = {} }; false') 100 | ); 101 | }); 102 | 103 | it('should deopt when variable is changed inside any of the functions', function () { 104 | expect( 105 | test( 106 | 'function a() { process = {}; DEBUG = true }; process.env.NODE_ENV; DEBUG;', 107 | {process: {env: { NODE_ENV: 'development'}}, DEBUG: false} 108 | ) 109 | ).toEqual( 110 | trim('function a() { process = {}; DEBUG = true }; process.env.NODE_ENV; DEBUG') 111 | ); 112 | }); 113 | 114 | it('should deopt only things that changed - multiple defs test', function () { 115 | expect( 116 | test( 117 | 'function a() { process.env.A = "hello"; }; process.env.A; process.env.B; DEBUG', 118 | {process: {env: { A: 'hi', B: 'world'}}, DEBUG: true} 119 | ) 120 | ).toEqual( 121 | trim('function a() { process.env.A="hello"; }; process.env.A; "world"; true') 122 | ); 123 | }); 124 | 125 | it('should not convert identifiers in random places', function () { 126 | expect( 127 | test( 128 | 'function a(DEBUG) {}; DEBUG;', 129 | {DEBUG: true} 130 | ) 131 | ).toEqual( 132 | trim('function a(DEBUG) {}; true;') 133 | ); 134 | }); 135 | 136 | it('should Throw for circular references', function () { 137 | const a = {}; 138 | a.b = a; 139 | expect( 140 | () => test('a', a) 141 | ).toThrow(); 142 | }); 143 | 144 | it('should NOT replace sub expressions of Member Expressions', function () { 145 | expect( 146 | test( 147 | 'function a() { DEBUG.process.env.DEBUG }', 148 | { process: { env: { DEBUG: 'dev' } } } 149 | ) 150 | ).toEqual( 151 | trim('function a() { DEBUG.process.env.DEBUG }') 152 | ); 153 | }); 154 | }); 155 | -------------------------------------------------------------------------------- /declarations/yargs.js: -------------------------------------------------------------------------------- 1 | // Source - https://github.com/flowtype/flow-typed/blob/master/definitions/npm/yargs_v4.x.x/flow_%3E%3Dv0.23.x/yargs_v4.x.x.js 2 | 3 | declare module 'yargs' { 4 | declare type Argv = {_: Array, [key: string]: mixed}; 5 | 6 | declare type Options = $Shape<{ 7 | alias: string, 8 | array: boolean, 9 | choices: Array, 10 | config: boolean, 11 | configParser: (configPath: string) => {[key: string]: mixed}, 12 | count: boolean, 13 | default: any, // FIXME 14 | defaultDescription: string, 15 | demand: boolean, 16 | desc: string, 17 | describe: string, 18 | description: string, 19 | global: boolean, 20 | group: string, 21 | nargs: number, 22 | normalize: boolean, 23 | number: boolean, 24 | require: boolean, 25 | required: boolean, 26 | requiresArg: boolean, 27 | string: boolean, 28 | type: 'array' | 'boolean' | 'count' | 'number' | 'string', 29 | }>; 30 | 31 | declare type DescParseFn = (configPath: string) => Object; 32 | 33 | declare type ModuleObject = { 34 | command: string, 35 | describe: string, 36 | builder: {[key: string]: Options} | (yargsInstance: Yargs) => void, 37 | handler: (argv: Argv) => void, 38 | }; 39 | 40 | declare class Yargs { 41 | alias(toBeAliased: string, alias: string): this; 42 | argv: Argv; 43 | array(key: string|Array): this; 44 | boolean(paramter: string|Array): this; 45 | check(fn: (argv: Argv, options: Array) => ?bool): this; 46 | choices(key: string, allowed: Array): this; 47 | 48 | command( 49 | cmd: string, 50 | desc: string|false, 51 | builder?: {[key: string]: Options} | (yargsInstance: Yargs) => void, 52 | handler?: Function 53 | ): this; 54 | 55 | command( 56 | cmd: string, 57 | desc: string|false, 58 | module: ModuleObject, 59 | ): this; 60 | 61 | command( 62 | module: ModuleObject 63 | ): this; 64 | 65 | completion( 66 | cmd: string, 67 | description?: string, 68 | fn?: ( 69 | current: string, 70 | argv: Object, 71 | done: (competion: Array) => void 72 | ) => ?(Array|Promise>) 73 | ): this; 74 | 75 | config( 76 | key: string, 77 | description?: string|DescParseFn, 78 | parseFn?: DescParseFn 79 | ): this; 80 | 81 | count(name: string): this; 82 | 83 | default(defaultObject: { [paramter: string]: any }): this; 84 | default(parameter: string, value: any): this; 85 | 86 | // Alias of require()! 87 | demand(key: string, msg: string | boolean): this; 88 | demand(count: number, max?: number, msg?: string | boolean): this; 89 | 90 | describe(key: string, desc: string): this; 91 | describe(describeObject: { [key: string]: string }): this; 92 | 93 | detectLocale(shouldI: bool): this; 94 | 95 | env(prefix?: string): this; 96 | 97 | epilog(epi: string): this; 98 | epilogue(epi: string): this; 99 | 100 | example(cmd: string, desc: string): this; 101 | 102 | exitProcess(enable: bool): this; 103 | 104 | fail(fn: (failureMessage: string) => mixed): this; 105 | 106 | global(globals: string | Array): this; 107 | 108 | group(key: string | Array, groupName: string): this; 109 | 110 | help(option?: string, desc?: string): this; 111 | 112 | implies(keyA: string, keyB: string): this; 113 | implies(keys: { [key: string]: string }): this; 114 | 115 | locale( 116 | locale: 'de' | 'en' | 'es' | 'fr' | 'id' | 'it' | 'ja' | 'ko' | 'nb' | 117 | 'pirate' | 'pl' | 'pt' | 'pt_BR' | 'tr' | 'zh' 118 | ): this; 119 | locale(): string; 120 | 121 | nargs(key: string, count: number): this; 122 | 123 | normalize(key: string): this; 124 | 125 | number(key: string | Array): this; 126 | 127 | option(key: string, options: Options): this; 128 | option(optionMap: { [key: string]: Options}): this; 129 | 130 | options(key: string, options: Options): this; 131 | options(optionMap: { [key: string]: Options}): this; 132 | 133 | parse(args: string | Array): Argv; 134 | 135 | pkgConf(key: string, cwd?: string): this; 136 | 137 | // Alias of demand()! 138 | require(key: string, msg: string | boolean): this; 139 | require(count: number, max?: number, msg?: string | boolean): this; 140 | 141 | requiresArg(key: string | Array): this; 142 | 143 | reset(): this; 144 | 145 | showCompletionScript(): this; 146 | 147 | showHelp(consoleLevel: 'error' | 'log' | 'warn'): this; 148 | 149 | showHelpOnFail(enable: bool, message?: string): this; 150 | 151 | strict(): this; 152 | 153 | string(key: string | Array): this; 154 | 155 | updateLocale(obj: {[key: string]: string}): this; 156 | updateStrings(obj: {[key: string]: string}): this; 157 | 158 | usage(message: string, opts?: {[key: string]: Options}): this; 159 | 160 | version( 161 | option?: string | () => string, 162 | description?: string | Function, 163 | version?: string | Function 164 | ): this; 165 | 166 | wrap(columns: number | null): this; 167 | } 168 | 169 | declare var exports: Yargs; 170 | } 171 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # babel-minify 2 | 3 | # DISCONTINUE NOTICE 4 | 5 | This project is discontinued here and I'm contributing here - 6 | 7 | https://github.com/babel/babili 8 | 9 | Thanks for your interest !!! 10 | 11 | --------------- 12 | 13 | Some tools, babel plugins and presets to minify ES6+ code or whatever code babel understands. 14 | 15 | [![Build Status](https://travis-ci.org/boopathi/babel-minify.svg?branch=master)](https://travis-ci.org/boopathi/babel-minify) 16 | 17 | ## Try Online 18 | 19 | https://boopathi.in/babel-minify/ 20 | 21 | # :rotating_light: WARNING: EXPERIMENTAL 22 | 23 | ## Track 24 | 25 | + [x] [mangle](https://github.com/boopathi/babel-minify/tree/master/packages/babel-plugin-transform-mangle) 26 | + [x] [mangle-options](https://github.com/boopathi/babel-minify/tree/master/packages/babel-plugin-transform-mangle#options) - `keep_fnames`, `topLevel`, `eval`, `except` 27 | + [x] [dead_code](https://www.npmjs.com/package/babel-plugin-transform-dead-code-elimination) 28 | + [x] [conditionals](https://github.com/boopathi/babel-minify/tree/master/packages/babel-plugin-transform-conditionals) 29 | + [x] [global_defs](https://github.com/boopathi/babel-minify/tree/master/packages/babel-plugin-transform-global-defs) 30 | + [x] [evaluate](https://github.com/boopathi/babel-minify/tree/master/packages/babel-plugin-transform-evaluate) 31 | + [x] [drop_debugger](https://www.npmjs.com/package/babel-plugin-transform-remove-debugger) 32 | + [x] [drop_console](https://www.npmjs.com/package/babel-plugin-transform-remove-console) 33 | + [x] properties - [member-expression-literals](https://www.npmjs.com/package/babel-plugin-transform-member-expression-literals), [property-literals](https://www.npmjs.com/package/babel-plugin-transform-property-literals) 34 | + [x] [join_vars](https://www.npmjs.com/package/babel-plugin-transform-merge-sibling-variables) 35 | + [x] booleans - [minify booleans](https://www.npmjs.com/package/babel-plugin-transform-minify-booleans) 36 | + [ ] unsafe - [undefined-to-void](https://www.npmjs.com/package/babel-plugin-transform-undefined-to-void), [simplify-comparison-operators](https://www.npmjs.com/package/babel-plugin-transform-simplify-comparison-operators), [function-to-arrow](https://github.com/boopathi/babel-minify/tree/master/packages/babel-plugin-transform-function-to-arrow) 37 | + [ ] sequences 38 | + [ ] if_return 39 | + [ ] cascade 40 | + [ ] keep_fargs 41 | + [x] keep_fnames - [mangle-options](https://github.com/boopathi/babel-minify/tree/master/packages/babel-plugin-transform-mangle#options), [function-to-arrow-options](https://github.com/boopathi/babel-minify/tree/master/packages/babel-plugin-transform-function-to-arrow#options) 42 | 43 | ## Packages overview 44 | 45 | ### [gulp-babel-minify](https://github.com/boopathi/babel-minify/blob/master/packages/gulp-babel-minify) 46 | 47 | ```js 48 | import minify from 'gulp-babel-minify'; 49 | 50 | gulp.task('min', function() { 51 | return gulp.src('build/temp/app.bundle.js') 52 | .pipe(minify(opts)) 53 | .pipe(gulp.dest('build/')); 54 | }) 55 | ``` 56 | 57 | ### [babel-minify](https://github.com/boopathi/babel-minify/blob/master/packages/babel-minify) 58 | 59 | + Node API + CLI 60 | 61 | ```js 62 | import minify from 'babel-minify'; 63 | minify(inputCode, { 64 | conditionals: true, 65 | drop_debugger: true 66 | }); 67 | ``` 68 | 69 | More details here - https://github.com/boopathi/babel-minify/blob/master/packages/babel-minify 70 | 71 | ### [babel-preset-min](https://github.com/boopathi/babel-minify/blob/master/packages/babel-preset-min) 72 | 73 | This is a preset that uses the default options of [babel-minify](https://github.com/boopathi/babel-minify/tree/master/packages/babel-minify) 74 | 75 | **WARNING:** This might cause some regression, depending on what other plugins and presets you use with this preset - because all the plugins are applied in one pass by default in babel. You can enable the `passPerPreset` option in babel, but then all the `babel-minify` plugins are still applied in one pass. So, consider using `babel-minify` NodeAPI or CLI or Gulp task with the [options](https://github.com/boopathi/babel-minify/tree/master/packages/babel-minify#options) - `plugins: []` and `presets: []` to pass your other plugins and presets. 76 | 77 | ```json 78 | { 79 | "presets": ["min"], 80 | "comments": false, 81 | "compact": true, 82 | "minified": true 83 | } 84 | ``` 85 | 86 | ## Sample App Usage 87 | 88 | When you bundle your code, remember to split your bundle into multiple packages or at least `vendor` and your `app` code separately. Usually, the vendor code will be ES5 compatible and UglifyJS does a better job here. And all the code you write is mostly ES6. You may want to ship this ES6 code to browsers. So we can pass this ES6 code via babel using a specific set of plugins applied in some fashion and make it do the optimizations and minification for you. 89 | 90 | **webpack.config.js** 91 | 92 | ```js 93 | // webpack.config.js 94 | module.exports = { 95 | entry: { 96 | app: './src/app.js' 97 | vendor: ['react', 'react-router', 'lodash', 'my-polyfills'] 98 | }, 99 | output: { 100 | path: 'build/webpack', 101 | filename: '[name].bundle.js' 102 | } 103 | plugins: [ 104 | new webpack.optimize.CommonsChunkPlugin('vendor') 105 | ] 106 | } 107 | ``` 108 | 109 | So, this would generate two files - `vendor.bundle.js` & `app.bundle.js` 110 | 111 | **gulpfile.js** 112 | 113 | ```js 114 | const uglify = require('gulp-uglify'); 115 | const minify = require('gulp-babel-minify'); 116 | const webpack = require('webpack'); 117 | const config = require('./webpack.config.js'); 118 | 119 | gulp.task('webpack', function(cb) { 120 | webpack(config, (err, stats) => { 121 | if (err) return cb(err); 122 | console.log(stats.toString()); 123 | cb(); 124 | }); 125 | }); 126 | 127 | gulp.task('minify-vendor', ['webpack'], function() { 128 | return gulp.src('build/webpack/vendor.bundle.js') 129 | .pipe(uglify()) 130 | .pipe(gulp.dest('build/minified')); 131 | }); 132 | 133 | gulp.task('minify-app', ['webpack'], function() { 134 | return gulp.src('build/webpack/app.bundle.js') 135 | .pipe(minify()) 136 | .pipe(gulp.dest('build/minified')); 137 | }); 138 | ``` 139 | 140 | ## LICENSE 141 | 142 | http://boopathi.mit-license.org 143 | -------------------------------------------------------------------------------- /packages/babel-minify/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /*::import type {Plugin, Preset} from 'Babel';*/ 3 | import {transform} from 'babel-core'; 4 | import D from './defaults'; 5 | 6 | // plugins 7 | import manglePlugin from 'babel-plugin-transform-mangle'; 8 | import evaluatePlugin from 'babel-plugin-transform-evaluate'; 9 | import conditionalsPlugin from 'babel-plugin-transform-conditionals'; 10 | import removeDebugger from 'babel-plugin-transform-remove-debugger'; 11 | import removeConsole from 'babel-plugin-transform-remove-console'; 12 | import deadCodeElimination from 'babel-plugin-transform-dead-code-elimination'; 13 | import memberExpressionLiterals from 'babel-plugin-transform-member-expression-literals'; 14 | import mergeSiblingVariables from 'babel-plugin-transform-merge-sibling-variables'; 15 | import minifyBooleans from 'babel-plugin-transform-minify-booleans'; 16 | import propertyLiterals from 'babel-plugin-transform-property-literals'; 17 | import simplifyComparisonOperators from 'babel-plugin-transform-simplify-comparison-operators'; 18 | import undefinedToVoid from 'babel-plugin-transform-undefined-to-void'; 19 | import functionToArrow from 'babel-plugin-transform-function-to-arrow'; 20 | import globalDefsPlugin from 'babel-plugin-transform-global-defs'; 21 | 22 | /** 23 | * The main function of the minifier 24 | */ 25 | export default function BabelMinify(inputCode /*:string*/, { 26 | mangle = D.mangle, 27 | 28 | dead_code = D.dead_code, 29 | conditionals = D.conditionals, 30 | evaluate = D.evaluate, // eval constant expressions 31 | drop_debugger = D.drop_debugger, 32 | drop_console = D.drop_console, 33 | properties = D.properties, 34 | join_vars = D.join_vars, 35 | booleans = D.booleans, 36 | unsafe = D.unsafe, 37 | keep_fnames = D.keep_fnames, 38 | 39 | // global-defs 40 | global_defs = D.global_defs, 41 | 42 | // source maps 43 | sourceMaps = D.sourceMaps, 44 | 45 | // input sourcemap 46 | inputSourceMap = D.inputSourceMap, 47 | 48 | // number of passes 49 | passes = D.passes, 50 | 51 | // passed on to babel transform to tell whether to use babelrc 52 | babelrc = D.babelrc, 53 | 54 | // should there be any other plugins added to this build process 55 | plugins = D.plugins, 56 | 57 | // should there be any other presets 58 | presets = D.presets, 59 | 60 | // if false, babel-minify can give a list of plugins to use as a preset 61 | minify = D.minify, 62 | } /*:MinifierOptions*/ = {}) /*:MinifierOutput*/ { 63 | 64 | if (typeof inputCode !== 'string' && minify) throw new Error('Invalid Input'); 65 | 66 | /** 67 | * The final list of plugins that are applied in babel transform 68 | * This is the first list that's preffered in babel transform, the plugins 69 | * that go into this take one pass, plugins that prefer separate passes go into 70 | * the minifyPresets 71 | */ 72 | let minifyPlugins /*:Plugin[]*/ = []; 73 | 74 | /** 75 | * The list of presets that are applied in SEPARATE passes 76 | */ 77 | let minifyPresets /*:Preset[]*/ = []; 78 | 79 | evaluate && minifyPlugins.push(evaluatePlugin); 80 | drop_debugger && minifyPlugins.push(removeDebugger); 81 | drop_console && minifyPlugins.push(removeConsole); 82 | properties && minifyPlugins.push(memberExpressionLiterals); 83 | properties && minifyPlugins.push(propertyLiterals); 84 | join_vars && minifyPlugins.push(mergeSiblingVariables); 85 | booleans && minifyPlugins.push(minifyBooleans); 86 | unsafe && minifyPlugins.push(undefinedToVoid); 87 | unsafe && minifyPlugins.push(simplifyComparisonOperators); 88 | unsafe && minifyPlugins.push([functionToArrow, { keep_fnames }]); 89 | 90 | if (Object.keys(global_defs).length > 0) { 91 | minifyPlugins.push([globalDefsPlugin, { 92 | global_defs 93 | }]); 94 | } 95 | 96 | /** 97 | * Append all user passed plugins to minifyPlugins 98 | */ 99 | minifyPlugins = minifyPlugins.concat(plugins); 100 | 101 | /** 102 | * Things that remove code or replace code in a major way, 103 | * we just use then in separate presets to enable them to be 104 | * under separate passes 105 | */ 106 | if (dead_code) { 107 | minifyPresets.push({plugins: [deadCodeElimination]}); 108 | } 109 | if (conditionals) { 110 | minifyPresets.push({plugins: [conditionalsPlugin]}); 111 | } 112 | 113 | /** 114 | * Append all user passed presets to passes 115 | */ 116 | minifyPresets = [...minifyPresets, ...presets]; 117 | 118 | /** 119 | * Keep mangler to be in the last of the presets 120 | * I don't know why clearly, but mangler seems to disrupt everything, so 121 | * I just keep it as the last pass 122 | */ 123 | if (mangle) { 124 | let mangleOpts /*MangleOptions*/ = {}; 125 | 126 | if (typeof mangle === 'boolean') { 127 | mangleOpts = { 128 | keep_fnames 129 | }; 130 | } else if (typeof mangle === 'object') { 131 | /** 132 | * keep_fnames in mangle overrides global keep_fnames 133 | */ 134 | mangleOpts = mangle; 135 | } else { 136 | throw new TypeError('Expected an object or boolean for mangle'); 137 | } 138 | 139 | minifyPresets.push({ 140 | plugins: [ 141 | [manglePlugin, mangleOpts] 142 | ] 143 | }); 144 | } 145 | 146 | // if minify is false, return the plugins list to be used elsewhere 147 | // maybe move this to a separate file later 148 | if (!minify) return { plugins: minifyPlugins, presets: minifyPresets }; 149 | 150 | let result /*BabelResult*/ = { 151 | code: inputCode, 152 | map: inputSourceMap, 153 | toString() {} 154 | }; 155 | 156 | while (passes-- > 0) { 157 | result = transform(result.code, { 158 | babelrc, 159 | comments: false, 160 | compact: true, 161 | minified: true, 162 | passPerPreset: true, 163 | presets: minifyPresets, 164 | plugins: minifyPlugins, 165 | sourceMaps, 166 | inputSourceMap: result.map 167 | }); 168 | } 169 | 170 | result.toString = () => result.code; 171 | 172 | return result; 173 | } 174 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-mangle/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /*::import type {NodePath, Binding, Scope, Node, PluginOptions} from 'Babel';*/ 3 | import nameGenerator from './namegen'; 4 | 5 | /** 6 | * These entities can have a `name` property which should be preserved when 7 | * `keep_fnames` is true. 8 | * 9 | * Arrow functions don't have a name. 10 | * 11 | * Methods are anyway not renamed as they are object properties 12 | * 13 | */ 14 | function isFunction(binding /* :Binding */) /*:boolean*/ { 15 | return binding.path.isFunctionExpression() || 16 | binding.path.isFunctionDeclaration() || 17 | binding.path.isClassDeclaration() || 18 | binding.path.isClassExpression(); 19 | } 20 | 21 | /** 22 | * Eval works in a way that, anything inside eval can access 23 | * all variables in ALL of its parent scopes 24 | * 25 | * Also, eval cannot be changed in strict mode like - let eval = 0; 26 | * So we don't have to find the data flow from the eval() expression 27 | * 28 | * We traverse through the path and detect any usage of eval 29 | */ 30 | function isEval(path) { 31 | const evalPaths = new Set(); 32 | const result = { 33 | direct: false, 34 | indirect: false 35 | }; 36 | 37 | path.traverse({ 38 | /** 39 | * Traversal enter enters the CallExpression first 40 | */ 41 | CallExpression(evalPath) { 42 | const callee = evalPath.get('callee'); 43 | if (callee.isIdentifier() && callee.node.name === 'eval') { 44 | result.direct = true; 45 | evalPaths.add(callee); 46 | } 47 | }, 48 | Identifier(evalPath) { 49 | if (evalPath.node.name === 'eval') { 50 | if (!evalPaths.has(evalPath)) { 51 | result.indirect = true; 52 | } 53 | } 54 | } 55 | }); 56 | 57 | return result; 58 | } 59 | 60 | /** 61 | * Given the predicates, filter the Exceptions from getting mangled 62 | * Mangle option: except 63 | */ 64 | function isExcept(binding /*:string*/, except /*:any[]*/) /*:bool*/ { 65 | for (let i = 0; i < except.length; i++) { 66 | const predicate = except[i]; 67 | 68 | switch (typeof predicate) { 69 | case 'string': 70 | if (predicate === binding) return true; 71 | break; 72 | 73 | case 'function': 74 | if (predicate(binding)) return true; 75 | break; 76 | 77 | // For regular expressions 78 | case 'object': 79 | if (predicate.test(binding)) return true; 80 | break; 81 | } 82 | } 83 | return false; 84 | } 85 | 86 | function renameIdentifiers(path /* :NodePath */, { 87 | opts: { 88 | keep_fnames = false, 89 | eval: _eval = false, 90 | except = [], 91 | } = {} 92 | } /*:ManglePluginOptions*/ = {}) { 93 | /** 94 | * Handle eval() - Direct calls 95 | * 96 | * If _eval is enabled/true, then we can mangle the names 97 | * If _eval is disabled/false, then we should check for eval in sub paths 98 | * 99 | * We don't have to worry about handling new Function() here as 100 | * they are bound in global scope 101 | */ 102 | if (!_eval && isEval(path).direct) return; 103 | 104 | const bindings /* :Object */ = path.scope.getAllBindings(); 105 | 106 | const ownBindings /* :string[] */ = Object.keys(bindings).filter(b => path.scope.hasOwnBinding(b)); 107 | const names = nameGenerator(); 108 | 109 | ownBindings 110 | /** 111 | * Filter all except passed in from the user 112 | * Don't mangle them 113 | */ 114 | .filter(b => !isExcept(b, except)) 115 | /** 116 | * If the binding is already just 1 character long, 117 | * there is no point in mangling - also, this saves us from expiring the 118 | * single character names during multiple passes. 119 | */ 120 | .filter(b => b.length !== 1) 121 | /** 122 | * keep_fnames 123 | * This is useful for functions which depend on fn.name 124 | */ 125 | .filter(b => { 126 | if (!keep_fnames) return true; 127 | const binding = path.scope.getBinding(b); 128 | if (!binding) { 129 | throw new TypeError('[mangle] Unexpected error. Binding not found'); 130 | } 131 | return !isFunction(binding); 132 | }) 133 | /** 134 | * Iterate through the possible names one by one until we 135 | * find a suitable binding that doesn't conflict with existing ones 136 | */ 137 | .map(b => { 138 | let _next = names.next(); 139 | if (_next.done) throw new Error('Name generator stopped'); 140 | 141 | let next /*:string*/ = _next.value; 142 | while (path.scope.hasBinding(next) || path.scope.hasGlobal(next) || path.scope.hasReference(next)) { 143 | _next = names.next(); 144 | if (_next.done) throw new Error('Name generator stopped'); 145 | next = _next.value; 146 | } 147 | path.scope.rename(b, next); 148 | }); 149 | } 150 | 151 | /** 152 | * Mangle Plugin 153 | */ 154 | export default function Mangle() { 155 | return { 156 | visitor: { 157 | Program(path /*:NodePath*/, options /*:ManglePluginOptions*/) { 158 | if (options.opts && options.opts.topLevel) { 159 | /** 160 | * If we have a winner, we don't care whether we use 161 | * indirect eval or direct eval or new Function etc... 162 | */ 163 | if (options.opts.eval) { 164 | renameIdentifiers(path, options); 165 | return; 166 | } 167 | 168 | /** 169 | * new Function() works in a way such that it is restricted 170 | * to access only it's variables declared in the local scope 171 | * and the variables in the global scope 172 | * 173 | * OR 174 | * 175 | * the function is created in the global scope 176 | */ 177 | let isNewFn = false; 178 | 179 | path.traverse({ 180 | NewExpression(newPath) { 181 | const callee = newPath.get('callee'); 182 | if (callee.isIdentifier() && callee.node.name === 'Function') { 183 | isNewFn = true; 184 | } 185 | } 186 | }); 187 | 188 | let isIndirectEval = isEval(path).indirect; 189 | 190 | if (!isNewFn && !isIndirectEval) { 191 | renameIdentifiers(path, options); 192 | } 193 | } 194 | }, 195 | BlockStatement: renameIdentifiers 196 | } 197 | }; 198 | } 199 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-mangle/test/index.js: -------------------------------------------------------------------------------- 1 | import mangle from '../src'; 2 | 3 | function test(input, options) { 4 | return trim(transform(input, { 5 | plugins: void 0 !== options ? [ [ mangle, options ] ] : [mangle], 6 | babelrc: false, 7 | comments: false 8 | }).code); 9 | } 10 | 11 | function testGlobals(input) { 12 | return test(input, { topLevel: true }); 13 | } 14 | function testFnames(input) { 15 | return test(input, { keep_fnames: true }); 16 | } 17 | function testExcept(input, except) { 18 | return test(input, { except }); 19 | } 20 | function testEval(input) { 21 | return test(input, { eval: true }); 22 | } 23 | 24 | // fixtures with default options 25 | const fixtures = { 26 | dont_mangle_globals: { 27 | input: '{ var someGlobalName = 0; } function blahblahblah() {}', 28 | expected: '{ var someGlobalName = 0; } function blahblahblah() {}', 29 | }, 30 | exports_and_imports: { 31 | input: 'import $ from "jQuery"; export default function Blah() {}', 32 | expected: 'import $ from "jQuery"; export default function Blah() {}', 33 | }, 34 | inner_scopes: { 35 | input: 'function a() { var longname = 1; var longlongname = 1 }', 36 | expected: 'function a() { var b = 1; var c = 1; }', 37 | }, 38 | nested_vars: { 39 | input: ` 40 | (function() { 41 | var longName = 1; let something = longName; const zero = something - 1; 42 | { var long2Name = 2; let something = long2Name; const zero = something - 1 } 43 | })(); 44 | `, 45 | expected: ` 46 | (function() { 47 | var a = 1; let b = a; const c = b - 1; 48 | { var d = 2; let e = d; const f = e - 1; } 49 | })(); 50 | `, 51 | }, 52 | // https://github.com/boopathi/babel-minify/issues/11 53 | global_global_vars: { 54 | input: 'function foo() { var bar = 1; var baz = a + bar }', 55 | expected: 'function foo() { var b = 1; var c = a + b }' 56 | }, 57 | properties_and_methods: { 58 | input: ` 59 | (function() { 60 | class Hello { method1() {} } 61 | function World() { this._world = 'earth'} 62 | var foo = { bar() {}, baz: true }; 63 | })(); 64 | `, 65 | expected: ` 66 | (function() { 67 | class b { method1() {} } 68 | function a() { this._world='earth' } 69 | var c = { bar() {}, baz: true } 70 | })(); 71 | ` 72 | }, 73 | // https://github.com/boopathi/babel-minify/issues/7 74 | vars_in_blocks: { 75 | input: 'function foo() { var bar; { var bar; function baz() {} } }', 76 | expected: 'function foo() { var a; { var a; function b () {} }}' 77 | } 78 | }; 79 | 80 | describe('babel-plugin-transform-mangle', function () { 81 | // fixtures 82 | Object.keys(fixtures).forEach(name => { 83 | it('should work for the fixture - ' + name, function () { 84 | expect( 85 | test(fixtures[name].input) 86 | ).toEqual( 87 | trim(fixtures[name].expected) 88 | ); 89 | }); 90 | }); 91 | 92 | // mangle_globals 93 | it('should mangle_globals when set to true', function () { 94 | expect( 95 | testGlobals('var foo = 1; let bar = 0; import baz from "baz"') 96 | ).toEqual( 97 | trim('var a = 1; let b = 0; import c from "baz"') 98 | ); 99 | }); 100 | 101 | it('should NOT treat block functions as globals', function () { 102 | expect( 103 | testGlobals('function foo() {} { function foo() {} } foo() ') 104 | ).toEqual( 105 | trim('function a() {} { function b() {} } a()') 106 | ); 107 | }); 108 | 109 | // keep_fnames 110 | it('should preserve class names when keep_fnames is true', function () { 111 | expect( 112 | testFnames('function foo() { class bar {} }') 113 | ).toEqual( 114 | trim('function foo() { class bar{} }') 115 | ); 116 | expect( 117 | testFnames('function foo() { var bar = class bar {} }') 118 | ).toEqual( 119 | trim('function foo() { var a = class bar {} }') 120 | ); 121 | }); 122 | 123 | it('should preserve function names when keep_fnames is true', function () { 124 | expect( 125 | testFnames('function foo() { function bar() {} }') 126 | ).toEqual( 127 | trim('function foo() { function bar() {} }') 128 | ); 129 | expect( 130 | testFnames('function foo() { var bar = { baz: function baz() {} } }') 131 | ).toEqual( 132 | trim('function foo() { var a = { baz: function baz() {} } }') 133 | ); 134 | }); 135 | 136 | // eval deopt 137 | it('should deopt all variables of all scopes accessible to eval', function () { 138 | expect( 139 | test('function baz () { var foo = 0; function bar() { eval() } }') 140 | ).toEqual( 141 | trim('function baz () { var foo = 0; function bar() { eval() } }') 142 | ); 143 | }); 144 | 145 | it('should NOT deopt variables that are not accessible to eval', function () { 146 | expect( 147 | test('function foo () { function bar() {eval()} function baz() { var evalCantSee; }}') 148 | ).toEqual( 149 | trim('function foo () { function bar() {eval()} function baz() { var a; }}') 150 | ); 151 | }); 152 | 153 | // eval option 154 | it('should Mangle names without deopt eval for eval=true', function () { 155 | expect( 156 | testEval('function baz () { var foo = 0; function bar() { eval() } }') 157 | ).toEqual( 158 | trim('function baz () { var b = 0; function a () { eval() }}') 159 | ); 160 | }); 161 | 162 | // new Function test 163 | it('should NOT mangle names when new Function is used and globals is true', function () { 164 | expect( 165 | testGlobals('function foo() { new Function(""); }') 166 | ).toEqual( 167 | trim('function foo() { new Function("") }') 168 | ); 169 | expect( 170 | test('function foo() { new Function(""); }') 171 | ).toEqual( 172 | trim('function foo() { new Function(""); }') 173 | ); 174 | }); 175 | 176 | // except option tests 177 | it('should NOT mangle names that are listed in except option', function () { 178 | expect( 179 | testExcept('(function() { var foo = 1; { let bar, baz; } })()', ['bar', 'foo']) 180 | ).toEqual( 181 | trim('(function() { var foo = 1; { let bar, a; } })()') 182 | ); 183 | }); 184 | 185 | // function 186 | it('should NOT mangle names in except', function () { 187 | expect( 188 | testExcept( 189 | 'function a() { var foobarbaz, barfoobaz, bazbarfoo, foobazbar }', 190 | [ name => name.substring(3, 6) === 'bar'] 191 | ) 192 | ).toEqual( 193 | trim('function a() { var foobarbaz, b, bazbarfoo, c }') 194 | ); 195 | }); 196 | 197 | // INDIRECT EVAL 198 | // https://github.com/boopathi/babel-minify/issues/31 199 | it('should mangle names when indirect eval is used', function () { 200 | expect( 201 | test('(function() { var bar = 10; var foo = eval; foo("") })()') 202 | ).toEqual( 203 | trim('(function() { var a = 10; var b = eval; b("") })()') 204 | ); 205 | }); 206 | 207 | // combinations 208 | // Globals with INDIRECT EVAL 209 | it('mangle topLevel with indirect eval', function () { 210 | expect( 211 | testGlobals('var foo, bar; var baz = eval') 212 | ).toEqual( 213 | trim('var foo, bar; var baz = eval') 214 | ); 215 | }); 216 | 217 | it('mangle topLevel with indirect eval and eval=true', function () { 218 | expect( 219 | test('var foo, bar; var baz = eval', { topLevel: true, eval: true }) 220 | ).toEqual( 221 | trim('var a, b; var c = eval') 222 | ); 223 | }); 224 | 225 | it('indirect eval with inner level', function () { 226 | expect( 227 | testGlobals('var foo; (function(){ var bar, baz = eval })()') 228 | ).toEqual( 229 | trim('var foo; (function() { var a, b = eval})()') 230 | ); 231 | }); 232 | 233 | it('should mangle names when the common pattern indirect eval is used', function () { 234 | expect( 235 | test('(() => { var foo = "hello"; var bar = (1, eval)("this") })()') 236 | ).toEqual( 237 | trim('(() => { var a = "hello"; var b = (1, eval)("this")})()') 238 | ); 239 | }); 240 | }); 241 | -------------------------------------------------------------------------------- /packages/babel-plugin-transform-global-defs/src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /*::import type {NodePath, Binding, Scope, Node, PluginOptions} from 'Babel';*/ 3 | 4 | import isPlainObject from 'lodash.isplainobject'; 5 | import isObjectLike from 'lodash.isobjectlike'; 6 | 7 | /*::type RawDefinition = [string, mixed]*/ 8 | 9 | /*::type Definition = { 10 | expr: string, 11 | value: mixed, 12 | allExpr: string[], 13 | root: string, 14 | }*/ 15 | 16 | export default function ({types: t} /*:PluginOptions*/) { 17 | /** 18 | * A plugin wide reference to the globalDefs passed from the user 19 | * with validations applied 20 | */ 21 | let globalDefs /*:Object*/ = {}; 22 | 23 | /** 24 | * A plugin wide reference for the deopts applied. 25 | * 26 | * A deopt is simply a dot notation of an object path 27 | * eg: a.b.c.d (or) a.b 28 | * which means all sub-paths of this path are deopted 29 | */ 30 | let deopts = new Set(); 31 | 32 | /** 33 | * A defintion is a modified form of globalDefs giving access 34 | * to all the paths of the object 35 | * 36 | * For example, 37 | * 38 | * global_defs = { a: { b: true, c: 50 } } 39 | * 40 | * definitions = 41 | * [ 42 | * { 43 | * root: "a", 44 | * expr: "a.b", 45 | * value: true, 46 | * allExpr: [ "a", "a.b" ] 47 | * }, 48 | * { 49 | * root: "a", 50 | * expr: "a.c", 51 | * value: 50, 52 | * allExpr: [ "a", "a.c" ] 53 | * } 54 | * ] 55 | */ 56 | let definitions /*:Definition[]*/= []; 57 | 58 | /** 59 | * deopts the expression, 60 | * just adds it to the plugin wide reference to deopts 61 | */ 62 | function deopt(expr /*:string*/) { 63 | // debug 64 | // console.log('deopting', expr); 65 | deopts.add(expr); 66 | } 67 | 68 | /** 69 | * Find if an expression is deopted. 70 | * 71 | * An expression is deopted IF any of its parent paths is deopted 72 | * 73 | * eg: 74 | * deopts = ["a.b"]; 75 | * isDeopt("a.b.c") //=> true 76 | */ 77 | function isDeopt(_expr /*:string|[string]*/) /*:bool*/{ 78 | let expr = Array.isArray(_expr) ? _expr : [_expr]; 79 | for (let subexpr of expr) { 80 | if (deopts.has(subexpr)) { 81 | return true; 82 | } 83 | } 84 | return false; 85 | } 86 | 87 | return { 88 | visitor: { 89 | Program(path /*:NodePath*/, { 90 | opts: { 91 | global_defs = {} 92 | } = {} 93 | } /*:GlobalDefsOptions*/ = {}) { 94 | /** 95 | * validate it's a plain object 96 | */ 97 | if (!isPlainObject(global_defs)) { 98 | throw new Error('global_defs must be a Plain Object'); 99 | } 100 | 101 | /** 102 | * Assign to globalDefs to be accessed by other hooks 103 | */ 104 | globalDefs = global_defs; 105 | 106 | // !important 107 | // flush the deopts, otherwise it is preserved in memory 108 | deopts = new Set(); 109 | 110 | definitions = objectToPaths(globalDefs).map(p => pathToDefn(p)); 111 | }, 112 | 113 | // TODO 114 | // UpdateExpression x++ things 115 | 116 | AssignmentExpression(path /*:NodePath*/) { 117 | const left = path.get('left'); 118 | 119 | /** 120 | * Traverse through every possibility of every path 121 | * obtained from the global_defs and match it with the 122 | * left side of the assignment, check that it's actually 123 | * modifying the global scoped var and not some other local 124 | * or outer scoped var, and deopt that expression. 125 | */ 126 | for (let {root, allExpr} of definitions) { 127 | if (path.scope.hasBinding(root) || !path.scope.hasGlobal(root)) return; 128 | 129 | for (let subexpr of allExpr) { 130 | /** 131 | * process = "blah"; 132 | */ 133 | if (left.isIdentifier() && left.node.name === subexpr) { 134 | return deopt(subexpr); 135 | } 136 | /** 137 | * process.env.NODE_ENV = "development"; 138 | */ 139 | else if (left.isMemberExpression() && left.matchesPattern(subexpr)) { 140 | return deopt(subexpr); 141 | } 142 | } 143 | } 144 | }, 145 | 146 | Identifier: { 147 | exit(path /*:NodePath*/) { 148 | /** 149 | * Only these paths can be the possible parents (of an Identifier), 150 | * for which a global definition can be safely replaced. 151 | */ 152 | let replaceablePaths = [ 153 | 'BinaryExpression', 154 | 'LogicalExpression', 155 | 'ExpressionStatement', 156 | 'Conditional', 157 | 'SwitchStatement', 158 | 'SwitchCase', 159 | 'WhileStatement', 160 | 'DoWhileStatement', 161 | 'ForStatement' 162 | ]; 163 | 164 | let replaceable = false; 165 | 166 | for (let i of replaceablePaths) { 167 | if (path.parentPath['is'+i]()) { 168 | replaceable = true; 169 | } 170 | } 171 | 172 | if (!replaceable) return; 173 | 174 | /** 175 | * Since it's just an Identifier, it's enough that 176 | * we traverse through all the roots of the definition, 177 | * determine if we can replace, and replace it 178 | * 179 | * So, we simply filter out other expressions from global definitions 180 | */ 181 | const {scope} = path; 182 | for (let {root, expr, value} of definitions) { 183 | if (root !== expr) continue; 184 | 185 | if (!scope.hasBinding(root) && scope.hasGlobal(root) && !isDeopt(root)) { 186 | path.replaceWith(t.valueToNode(value)); 187 | } 188 | } 189 | } 190 | }, 191 | 192 | MemberExpression: { 193 | exit(path /*:NodePath*/) { 194 | /** 195 | * Replace all member expressions that are 196 | * 1. not deopted 197 | * 2. not locals or vars in outer scopes 198 | */ 199 | const {scope} = path; 200 | for (let {root, expr, value, allExpr} of definitions) { 201 | if (root === expr) continue; 202 | 203 | if (path.matchesPattern(expr) && !scope.hasBinding(root) && scope.hasGlobal(root) && !isDeopt(allExpr)) { 204 | path.replaceWith(t.valueToNode(value)); 205 | } 206 | } 207 | } 208 | } 209 | } 210 | } 211 | } 212 | 213 | /** 214 | * Converts an expression in the form of array to dot notation and a value 215 | * 216 | * in => ['a', 'b', true] 217 | * out => { 218 | * expr: 'a.b', 219 | * value: true, 220 | * allExpr: ['a', 'a.b'], 221 | * root: 'a' 222 | * } 223 | */ 224 | function pathToDefn(path /*:RawDefinition*/) /*:Definition*/ { 225 | let _expr = [...path], possibilities = []; 226 | const value /*:mixed*/ = _expr.pop(); 227 | 228 | for (let i = 0; i < _expr.length; i++) { 229 | possibilities.push(_expr.slice(0, i+1)); 230 | } 231 | 232 | return { 233 | expr: exprToString(_expr), 234 | allExpr: possibilities.map(e => exprToString(e)), 235 | root: path[0], 236 | value 237 | }; 238 | } 239 | 240 | /** 241 | * Converts an expression represented in the form of array to dot notation 242 | * 243 | * in => ['a', 'b', 'c', 0, 'd'] 244 | * out => 'a.b.c[0].d' 245 | */ 246 | function exprToString(expr /*:RawDefinition*/) /*:string*/ { 247 | return expr.reduce((p, c) => { 248 | if (typeof c === 'number') { 249 | return `${p}[${c}]`; 250 | } else if (typeof c === 'string') { 251 | return p ? p + '.' +c : c; 252 | } else { 253 | throw new TypeError('Somehow value of expression came in to expression'); 254 | } 255 | }, ''); 256 | } 257 | 258 | /** 259 | * returns the list of paths that can be taken to reach a leaf 260 | * given an object 261 | * 262 | * eg: 263 | * obj = { a: 1, b: { a: 2, b: 3 }} 264 | * out => 265 | * [ 266 | * ['a', 1], 267 | * ['b', 'a', 2], 268 | * ['b', 'b', 3] 269 | * ] 270 | */ 271 | function objectToPaths(obj /*:Object*/) /*:RawDefinition[] */ { 272 | // To detect circular references 273 | let visited = new Set(); 274 | 275 | let paths /*RawDefinition[]*/ = []; 276 | 277 | function walk(o, state /*:[number|string]*/= []) { 278 | // detect cyclic references 279 | if (isObjectLike(o)) { 280 | if (visited.has(o)) { 281 | throw new Error('Circular reference in global_defs not supported'); 282 | } 283 | visited.add(o); 284 | } 285 | 286 | // traverse 287 | if (Array.isArray(o)) { 288 | for (let i = 0; i < o.length; i++) { 289 | walk(o[i], [...state, i]); 290 | } 291 | } else if (isPlainObject(o)) { 292 | const keys = Object.keys(o); 293 | for (let key of keys) { 294 | walk(o[key], [...state, key]); 295 | } 296 | } else if (isObjectLike(o)) { 297 | throw new TypeError('No support for Object like things' + o.toString()); 298 | } else { 299 | paths.push([...state, o]); 300 | } 301 | } 302 | 303 | walk(obj); 304 | return paths; 305 | } 306 | --------------------------------------------------------------------------------