Home

Awesome

#AMDclean

A build tool that converts AMD code to standard JavaScript.

Build Status NPM version

npm install amdclean --save-dev

Getting Started Video

Use Case

Single file client-side JavaScript libraries or web apps that want to use AMD and/or CommonJS modules to structure and build their code, but don't want any additional footprint.

Used By

Why

Many developers like to use the AMD and/or CommonJS (CJS) module APIs to write modular JavaScript, but do not want to include a full AMD or CJS loader (e.g. require.js), or shim (e.g. almond.js, browserify) because of file size/source code readability concerns.

By incorporating AMDclean.js into the build process, you no longer need to include Require.js or Almond.js in production, or use Browserify.

Since AMDclean rewrites your source code into standard JavaScript, it is a great fit for JavaScript library/web app authors who want a tiny download in one file after using the RequireJS Optimizer. AMDclean uses multiple different optimization algorithms to create the smallest file possible, while still making your code readable.

Restrictions

Note: Same restrictions as almond.js.

It is best used for libraries or apps that use AMD or CommonJS (using the cjsTranslate Require.js optimizer option) and optimize all modules into one file or multiple bundles. If you do not include Require.js or a similar loader, you cannot dynamically load code.

What is Supported

    {
      // Will not transform conditional AMD checks - Libraries use this to provide optional AMD support
      'transformAMDChecks': false
    }

Download

Node - npm install amdclean --save-dev

Web - Latest release

Usage

There are a few different ways that AMDclean can be used including:

Note: AMDclean does not have any module ordering logic, so if you do not use the RequireJS optimizer then you need to find another solution for resolving module dependencies before your files can be "cleaned".

AMDclean with the RequireJS Optimizer

onModuleBundleComplete: function (data) {
  var fs = module.require('fs'),
    amdclean = module.require('amdclean'),
    outputFile = data.path,
    cleanedCode = amdclean.clean({
      'filePath': outputFile
    });

  fs.writeFileSync(outputFile, cleanedCode);
}
module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    requirejs: {
      js: {
        options: {
          'findNestedDependencies': true,
          'baseUrl': 'src/js/app/modules',
          'optimize': 'none',
          'mainConfigFile': 'src/js/app/config/config.js',
          'include': ['first'],
          'out': 'src/js/app/exampleLib.js',
          'onModuleBundleComplete': function (data) {
            var fs = require('fs'),
              amdclean = require('amdclean'),
              outputFile = data.path;

            fs.writeFileSync(outputFile, amdclean.clean({
              'filePath': outputFile
            }));
          }
        }
      }
    }
  });
  grunt.loadNpmTasks('grunt-contrib-requirejs');
  grunt.registerTask('build', ['requirejs:js']);
  grunt.registerTask('default', ['build']);
};
gulp.task('build', function() {
  var requirejs = require('requirejs');

  requirejs.optimize({
    'findNestedDependencies': true,
    'baseUrl': './src/',
    'optimize': 'none',
    'include': ['first'],
    'out': './build/example.js',
    'onModuleBundleComplete': function(data) {
      var fs = require('fs'),
        amdclean = require('amdclean'),
        outputFile = data.path;

      fs.writeFileSync(outputFile, amdclean.clean({
        'filePath': outputFile
      }));
    }
  });
});

AMDclean as a Node Module

var amdclean = require('amdclean');
var code = 'define("exampleModule", function() {});'
var cleanedCode = amdclean.clean(code);

AMDclean as a Client-side Library

<script src="http://esprima.org/esprima.js"></script>
<script src="http://constellation.github.io/escodegen/escodegen.browser.js"></script>
<script src="https://rawgithub.com/Constellation/estraverse/master/estraverse.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.2.1/lodash.js"></script>
<script src="https://rawgithub.com/gfranko/amdclean/master/src/amdclean.js"></script>
var cleanedCode = amdclean.clean('define("example", [], function() { var a = true; });');

Requirements

Optional Dependencies

How it works

AMDclean uses Esprima to generate an AST (Abstract Syntax Tree) from the provided source code, estraverse to traverse and update the AST, and escodegen to generate the new standard JavaScript code.

Note: If you are interested in how this works, watch this presentation about building Static Code Analysis Tools.

Here are a few different techniques that AMDclean uses to convert AMD to standard JavaScript code:

Define Calls

AMD

define('example', [], function() {

});

Standard

var example;
example = undefined;

AMD

define('example', [], function() {
  var test = true;
});

Standard

var example;
example = function () {
    var test = true;
}();

AMD

define('example', [], function() {
  return function(name) {
    return 'Hello ' + name;
  };
});

Standard

var example;
example = function (name) {
  return 'Hello ' + name;
};

AMD

define('example', [], function() {
  return 'I love AMDclean';
});

Standard

var example;
example = 'I love AMDclean';

AMD

define('example', ['example1', 'example2'], function(one, two) {
  var test = true;
});

Standard

var example;
example = function (one, two) {
  var test = true; 
}(example1, example2);

AMD

define("backbone", ["underscore","jquery"], (function (global) {
    return function () {
        var ret, fn;
        return ret || global.Backbone;
    };
}(this)));

Standard

var backbone;
backbone = window.Backbone;

AMD

define('third',{
  exampleProp: 'This is an example'
});

Standard

var third;
third = {
  exampleProp: 'This is an example'
};

Require Calls

Note: require(['someModule']) calls, with no callback function, are removed from the built source code

AMD

require([], function() {
  var example = true;
});

Standard

(function () {
    var example = true;
}());

AMD

require(['anotherModule'], function(someModule) {
  var example = true;
});

Standard

(function (someModule) {
    var example = true;
}(anotherModule));

Optimization Algorithms

AMDclean uses a few different strategies to decrease file size:

Remove Unused Dependencies/Parameters

AMD

define('example', ['example1', 'example2'], function() {
  var test = true;
});

Standard

// Since no callback parameters were provided in the AMD code,
// the 'example1' and 'example2' dependencies/parameters were not added
var example;
example = function() {
  var test = true;
}();

Remove Exact Matching Dependencies/Parameters

AMD

define('example', ['example1', 'example2'], function(example1, anotherExample) {
  var test = true;
});

Standard

// Since the `example1` callback function parameter exactly matched
// the name of the `example1 dependency, it's `example1` dependency/parameter was removed
var example;
example = function(anotherExample) {
  var test = true;
}(example2);

Hoist Common Non-Matching Dependencies

AMD

define('example', ['example1'], function(firstExample) {
  var test = true;
});
define('anotherExample', ['example1'], function(firstExample) {
  var test = true;
});

Standard

// Since the `firstExample` callback function parameter was used more
// than once between modules, it was hoisted up and reused
var example, firstExample;
firstExample = example1;
example = function() {
  var test = true;
};
anotherExample = function() {
  var test = true;
};

Options

The amdclean clean() method accepts a string or an object. Below is an example object with all of the available configuration options:

amdclean.clean({
  // The source code you would like to be 'cleaned'
  'code': '',
  // Provide a source map for the code you'd like to 'clean'
  // Output will change from plain code to a hash: {code: ..., map: ...}
  // Where code is 'cleaned' code and map is the new source map
  'sourceMap': null,
  // Determines if certain aggressive file size optimization techniques
  // will be used to transform the soure code
  'aggressiveOptimizations': false,
  // The relative file path of the file to be cleaned.  Use this option if you
  // are not using the code option.
  // Hint: Use the __dirname trick
  'filePath': '',
  // The modules that you would like to set as window properties
  // An array of strings (module names)
  'globalModules': [],
  // All esprima API options are supported: http://esprima.org/doc/
  'esprima': {
    'comment': true,
    'loc': true,
    'range': true,
    'tokens': true
  },
  // All escodegen API options are supported: https://github.com/Constellation/escodegen/wiki/API
  'escodegen': {
    'comment': true,
    'format': {
      'indent': {
        'style': '  ',
        'adjustMultilineComment': true
      }
    }
  },
  // If there is a comment (that contains the following text) on the same line
  // or one line above a specific module, the module will not be removed
  'commentCleanName': 'amdclean',
  // The ids of all of the modules that you would not like to be 'cleaned'
  'ignoreModules': [],
  // Determines which modules will be removed from the cleaned code
  'removeModules': [],
  // Determines if all of the require() method calls will be removed
  'removeAllRequires': false,
  // Determines if all of the 'use strict' statements will be removed
  'removeUseStricts': true,
  // Determines if conditional AMD checks are transformed
  // e.g. if(typeof define == 'function') {} -> if(true) {}
  'transformAMDChecks': true,
  // Determines if a named or anonymous AMD module will be created inside of your conditional AMD check
  // Note: This is only applicable to JavaScript libraries, do not change this for web apps
  // If set to true: e.g. define('example', [], function() {}) -> define([], function() {})
  'createAnonymousAMDModule': false,
  // Allows you to pass an expression that will override shimmed modules return
  // values e.g. { 'backbone': 'window.Backbone' }
  'shimOverrides': {},
  // Determines how to prefix a module name with when a non-JavaScript
  // compatible character is found 
  // 'standard' or 'camelCase'
  // 'standard' example: 'utils/example' -> 'utils_example'
  // 'camelCase' example: 'utils/example' -> 'utilsExample'
  'prefixMode': 'standard',
  // A hook that allows you add your own custom logic to how each moduleName is
  // prefixed/normalized
  'prefixTransform': function(postNormalizedModuleName, preNormalizedModuleName) { return postNormalizedModuleName; },
  // Wrap any build bundle in a start and end text specified by wrap
  // This should only be used when using the onModuleBundleComplete RequireJS
  // Optimizer build hook
  // If it is used with the onBuildWrite RequireJS Optimizer build hook, each
  // module will get wrapped
  'wrap': {
    // This string is prepended to the file
    'start': ';(function() {\n',
    // This string is appended to the file
    'end': '\n}());'
  },
  // Configuration info for modules
  // Note: Further info can be found here - http://requirejs.org/docs/api.html#config-moduleconfig
  'config': {},
  // A hook that allows you add your own custom module variable assignment expression, very handly if you need to 
  // create your own modules global dictionary
  'IIFEVariableNameTransform': function(moduleName, moduleId){return 'GlobalModules[\'' + moduleId + '\'] = ' + moduleName; }
})

Unit Tests

All unit tests are written using the jasmine-node library and can be found in the test/specs/ folder. You can run the unit tests by typing: npm test or gulp test.

Contributing

Please send all PR's to the dev branch.

If your PR is a code change:

  1. Update the appropriate module inside of the src/modules directory.
  2. Add a Jasmine unit test to convert.js inside of the test/specs folder
  3. Install all node.js dev dependencies: npm install
  4. Install gulp.js globally: sudo npm install gulp -g
  5. Lint, Minify, and Run all unit tests with Gulp: gulp
  6. Verify that the minified output file has been updated in build/amdclean.min.js
  7. Send the PR!

Note: There is a gulp watch task that will automatically lint, minify, unit test, and build AMDclean whenever a module inside of the src/modules directory is changed. I recommend using it.

FAQ

After I build with AMDclean, I am getting JavaScript errors. What gives?

Why should I use AMDclean instead of Browserify?

Browserify Pros

Browserify Cons

AMDclean Pros

AMDclean Cons

Why should I use AMDclean instead of Almond.js?

Do I have to use the onModuleBundleComplete Require.js hook?

onModuleBundleComplete: function (data) {
var fs = require('fs'),
  amdclean = require('amdclean'),
  outputFile = data.path;
fs.writeFileSync(outputFile, amdclean.clean({
  'filePath': outputFile,
  'globalObject': true
}));
}

Does AMDclean use AMDclean to build itself?

Is AMDclean only for libraries, or can I use it for my web app?

My comments seem to be getting removed when I use AMDclean. What am I doing wrong?

What if I don't want all define() and require() method calls to be removed?

var amdclean = require('amdclean');
amdclean.clean({
   'code': 'define("randomExample", function() { console.log("I am a random example"); });',
   'ignoreModules': ['randomExample']
});

If there is not an associated module id, then you must put a comment with only the words amdclean on the same line or one line above the method in question. For example, amdclean would not remove the define() method below:

// amdclean
define('example', [], function() {});

If you want to use different text than amdclean, you can customize the comment name by using the commentCleanName option.

Why are define() method placeholder functions inserted into my source?

How would I expose one or more modules as a global window property?

I replaced Almond.js with AMDclean and my file is bigger. Why Is This?

I am building a JavaScript library and want to provide conditional AMD support, but AMDclean seems to be wiping away my if statement. How do I fix this?

I am building a JavaScript library and want to create a conditional anonymous AMD module, but Require.js and AMDclean seems to always setting a module ID. How do I fix this?

I don't like the way AMDclean normalizes the names of my modules with underscores. Can I change this?

Require.js supports passing module information, to one or more modules, with the config option. Does AMDclean support this?

I can't seem to get AMDclean 2.0 to work. What gives?

I'd like to use source map support. What to do?

var amdclean = require('amdclean'),
   cleaned = amdclean.clean({
       'sourceMap: '{...}', // this is the source map that you already have for the code below
       'code': 'define("randomExample", function() { console.log("I am a random example"); });',
       'wrap': false, // do not use wrap together with escodegen.sourceMapWithCode since it breaks the logic
       'esprima': {
           'source': 'myfile.js' // name of your file to appear in sourcemap
       },
       'escodegen': {
           'sourceMap': true,
           'sourceMapWithCode': true
       }
   });

Attention! Result in variable cleaned is an object {code: ..., map: ...} where code is your cleaned code and map is a source map. Read Escodegen Wiki for more info.

License

Copyright (c) 2014 Greg Franko Licensed under the MIT license.