Skip to content

Instantly share code, notes, and snippets.

@jaridmargolin
Last active February 11, 2024 22:47
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jaridmargolin/9010540 to your computer and use it in GitHub Desktop.
Save jaridmargolin/9010540 to your computer and use it in GitHub Desktop.
;(function (id, name, context, definition) {
// --------------------------------------------------------------------------
// Dependencies
//
// This is an attempt to make this library compatible with multiple module
// formats. Other UMD implementations do not take into account dependencies:
// Example: https://github.com/ForbesLindesay/umd/blob/master/template.js
//
// **NOTE: Named AMD modules are more suitable for libraries as it provides
// a consistent naming convention to access it by (anonymous AMD modules are
// better suited on an app level where files/paths may be changed).
// --------------------------------------------------------------------------
var deps = [];
// CommonJS
if (typeof module !== 'undefined' && module.exports) {
for (var i = 0, l = deps.length; i < l; i++) {
deps[i] = require(deps[i])
};
module.exports = definition.apply(context, deps);
// AMD
} else if (typeof define === 'function' && define.amd) {
define(id, deps, definition);
// STANDARD
} else {
context[name] = definition.apply(context, deps);
}
})('amdName', 'contextName', this, function () {
@jaridmargolin
Copy link
Author

And funny enough this discussion happening right now regarding backbone 1.1.1: https://twitter.com/jashkenas/status/434431220345475072

Specifically: jashkenas/backbone@ea29bd9

(function(root, factory) {

  // Set up Backbone appropriately for the environment. Start with AMD.
  if (typeof define === 'function' && define.amd) {
    define(['underscore', 'jquery', 'exports'], function(_, $, exports) {
      // Export global even in AMD case in case this script is loaded with
      // others that may still expect a global Backbone.
      root.Backbone = factory(root, exports, _, $);
    });

  // Next for Node.js or CommonJS. jQuery may not be needed as a module.
  } else if (typeof exports !== 'undefined') {
    var _ = require('underscore');
    factory(root, exports, _);

  // Finally, as a browser global.
  } else {
    root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$));
  }

}(this, function(root, Backbone, _, $) {

@jaridmargolin
Copy link
Author

@gfranko I am curious how you handle third party dependencies. amdclean does an amazing job at letting me package together my internal src files, however the question of how to expose my library as a whole still remains.

As a library I want to play well together with everybody :) While I see AMD as the optimal choice for browser centric javascript, I see more and more people adopting browserify as solution for packing together js applications.

Browserify exposes a umd flag which uses the following: https://github.com/ForbesLindesay/umd/blob/master/template.js . My issue here is that there is no thought into declaring external dependencies.

@jaridmargolin
Copy link
Author

Ok so my version does not play very well with r.js optimizer. However it is more or less than same as: https://github.com/umdjs/umd/blob/master/returnExports.js

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['b'], factory);
    } else if (typeof exports === 'object') {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory(require('b'));
    } else {
        // Browser globals (root is window)
        root.returnExports = factory(root.b);
    }
}(this, function (b) {
    //use b in some fashion.

    // Just return a value to define the module export.
    // This example returns an object, but the module
    // can return a function as the exported value.
    return {};
}));

Only addition I would make would be to pass a moduleID when defining an AMD module. I think libraries should have the names defined by default so that can more reliably be used in existing projects.

@jaridmargolin
Copy link
Author

Each time I create a library I follow this tedious process.

1.] Create intro.js

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['b'], factory);
    } else if (typeof exports === 'object') {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory(require('b'));
    } else {
        // Browser globals (root is window)
        root.returnExports = factory(root.b);
    }
}(this, function (b) {

2.] Manually enter my name, id, dependencies, etc..

3.] Create outro.js

}));

4.] Alter my Gruntfile.js

compile: {
  options: {
    .
    .
    wrap: {
      startFile: ['src/intro.js'],
      endFile: ['src/outro.js']
    }
  }
}

@jaridmargolin
Copy link
Author

Wondering if a there would be use for a tool that templates the UMD boiler and wraps a specified file. Supporting the following formats: https://github.com/umdjs/umd

Simple Usage

umdWrap('file-path.js', [dependency]);

// From this information we will deduce the following:
// moduleID --> 'file-path'
// name --> filePath

Advanced Usage

umdWrap('file-path.js', {
  type: 'returnExports',
  id: 'file-path-custom',
  name: 'filePathCustom',
  dependencies: [dependency]
});

@gfranko
Copy link

gfranko commented Feb 15, 2014

AMDClean Bugs

I cc'd you on two tickets regarding AMDClean that need to be fixed:

  1. Stop comments from being removed inside module declarations
  2. Stop AMD conditional checks from being changed to if(true){} for libraries (this is required for web apps to work)

I'll try to fix these in the next day or two.

Backbone Use Case

The Backbone node issue is that Jeremy did not think people would be using jQuery in a node environment, so he is not passing it to his library function. But luckily you can set Backbone.$ = require('jquery' as a workaround

Anonymous vs Named AMD Modules

I agree with you about exposing a library as a named AMD modules if it is expected to be a dependency for another library. If it is not expected to be a dependency for another library, then I think you should just expose it as an anonymous AMD module.

The benefits of exposing a library as a named AMD module is that other libraries that depend on it can now list it as a dependency in their own conditional define() methods.

A user will still most likely have to create a paths configuration alias name for each library, but it does not force them to add a shim configuration to declare the dependency ordering, since the libraries are taking care of that themselves.

The obvious downside is if a user renames a library path name to something other than the agreed upon name for a library, then Require.js will throw an error saying that a module, that a library depends on, does not exist. This can be solved by either fixing the name being set in the paths config, or just setting an additional shims config for the renamed library.

Handling Dependencies

Here are the important points:

  1. If I am in an AMD environment, I list all of my dependencies via agreed upon names
  2. If I am in a node environment, I reference all of my dependencies with their agreed upon module names
  3. If I am in a web environment, I reference all of my dependencies using their agree upon global names

Note: Something to keep in mind is that not every library is AMD compatible. Because of this, you should default to your web environment (even if there is an AMD loader on the page) if there is not an expected return value for a certain module.

Let's take a look at the AMDClean source to see how I handle these:

(function (root, factory, undefined) {
    'use strict';
    // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js, and plain browser loading
    if (typeof define === 'function' && define.amd) {
        if(typeof exports !== 'undefined') {
            factory.env = 'node';
        } else {
            factory.env = 'web';
        }
        factory.amd = true;
        define(['esprima', 'estraverse', 'escodegen', 'underscore'], function(esprima, estraverse, escodegen, underscore) {
            return factory({
                'esprima': esprima,
                'estraverse': estraverse,
                'escodegen': escodegen,
                'underscore': underscore
            }, root);
        });
    } else if (typeof exports !== 'undefined') {
        factory.env = 'node';
        factory(null, root);
    } else {
        factory.env = 'web';
        root.amdclean = factory(null, root);
    }
}(this, function cleanamd(amdDependencies, scope) {
    'use strict';
    amdDependencies = amdDependencies || {};
    // Environment - either node or web
    var codeEnv = cleanamd.env,
        that = scope,
        // Third-Party Dependencies
        esprima = (function() {
            if(cleanamd.amd && amdDependencies.esprima) {
                return amdDependencies.esprima;
            } else if(that && that.esprima) {
                return that.esprima;
            } else if(codeEnv === 'node') {
                return require('esprima');
            }
        }()),
        estraverse = (function() {
            if(cleanamd.amd && amdDependencies.estraverse) {
                return amdDependencies.estraverse;
            } else if(that && that.estraverse) {
                return that.estraverse;
            } else if(codeEnv === 'node') {
                return require('estraverse');
            }
        }()),
        escodegen = (function() {
            if(cleanamd.amd && amdDependencies.escodegen) {
                return amdDependencies.escodegen;
            } else if(that && that.escodegen) {
                return that.escodegen;
            } else if(codeEnv === 'node') {
                return require('escodegen');
            }
        }()),
        _ = (function() {
            if(cleanamd.amd && amdDependencies.underscore) {
                return amdDependencies.underscore;
            } else if(that && that._) {
                return that._;
            } else if(codeEnv === 'node') {
                return require('lodash');
            }
        }());
})); // End of amdclean module

Automating The Process

I think you definitely could automate this process (and I could clean up a bit of my AMDClean code), but you are going to have to work a lot of hours to do it, since there are a lot of conditions to think about. For example...

Lodash

Since so many people use lodash as an underscore replacement, I use underscore as my agreed upon AMD module name, but I use the lodash name to reference it in a node environment.

But regardless, I think it would be a good project to start.

Browserify

A lot of people are fine with forcing users to create shim configurations that declare the ordering and dependencies of modules. I prefer not making people do this while still allowing them the capability to do it.

@jaridmargolin
Copy link
Author

Hey man,

Much appreciated on the timely response.

Anonymous vs Named AMD Modules

I guess I didn't quite think through all of the ramifications of setting the module ID. The key used in the paths configuration really ends up being the defacto name to reference the module :/ I'm now second guessing my original thoughts and wondering if passing the module ID solves anything....

Handling Dependencies

  • AMD: module will ultimately be called via the key passed in the paths configuration.
  • CommonJS (node): module will be called via npm module name.
  • Global (web): called via the declared global name set in the UMD wrapper.

Handling Dependencies Continued (Library naming conventions)

* Example*: backbone.customs.js (a backbone binding for a validation library I wrote)

I'm thinking attaching a .js ext to my module name is only making my life harder. A convention I started using in order to distinguish between internal libraries we are using at my current employer, First Opinion. We have a module called endpoints (written in python) and I created enpoints.js. Anyways..

  • AMD: bb-customs to call it but then I pass it to my factory as bbCustoms
  • CommonJS (node): bb-customs set to var bbCustoms
  • Global (web): bbCustoms

Now if I create a library that depends on bb-customs. I don't want to package it with the library so I exclude it in my require.js config. It is then run through AMDClean and I have yet another naming convention to deal with.

src:

define(['bb-customs'], function ('bbCustoms') {
  return {};
});

output:

var  = function (bbCustoms) {
  return {};
}(bb_customs);

Now it is being passed into my module as: bb_customs and my UMD declaration now has account for all 3:

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['bb-customs'], factory);
    } else if (typeof exports === 'object') {
        module.exports = factory(require('bb-customs'));
    } else {
        // Browser globals (root is window)
        root['libName'] = factory(root.bbCustoms);
    }
}(this, function (bb_customs) {

So I am not sure what I am expecting you to say here :D Just feels like a mess to me. Maybe if there was a way in AMDClean to specify how I want names converted (camelCase rather than using underscores?).. This way I would at least only have to deal with bb-customs and bbCustoms.

AMDClean Source

I am still trying to digest what exactly is happening here. You are passing your dependencies in as an object... then in your actual source code you are defining them as scope vars and requiring (CommonJs) or grabbing (from browser window)if necessary.

Disreagrding implementation, does your approach actually differ in what it accomplishes compared to the UMD template here?

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['b'], factory);
    } else if (typeof exports === 'object') {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory(require('b'));
    } else {
        // Browser globals (root is window)
        root.returnExports = factory(root.b);
    }
}(this, function (b) {

Also, I may not be understanding the following Note:

"Note: Something to keep in mind is that not every library is AMD compatible. Because of this, you should default to your web environment (even if there is an AMD loader on the page) if there is not an expected return value for a certain module."

...Isn't this why we have the shim option?

Automating The Process

As for automating... There will be most difinetly be edge cases, and for those I can provide advanced options for correctly mapping. Over the next few days I will update this thread with an outline of the full interface and available options. Perhaps you could review so we can iron out as many details as possible before implementing.

And if you are happy with the final product, I think it could be a useful option to implement into AMDClean. Thoughts?


UPDATE:

May be a solved problem already. Have not had an opportunity to thoroughly investigate yet:
https://github.com/alexlawrence/grunt-umd

@gfranko
Copy link

gfranko commented Feb 18, 2014

I just released AMDClean 1.2.1, which added the prefixMode option. You can now have all of your modules use camelCase instead of underscores. If that's not enough, you can use the new prefixTransform function hook to customize the logic yourself.

Usually, the Require.js shim configuration will work (but there are exceptions). AMDClean depends on the escodegen library, which does not work even with the shim configuration: estools/escodegen#115 (comment)

The AMDClean approach still works (even though Escodegen does not work with Require.js), since it also checks if there is a global window.escodegen object that is declared (which there is) and a window.escodegen.generate method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment