definejs

0.2.9 • Public • Published

DefineJS

Gitter Build Status Code Climate

DefineJS is a lightweight implementation of AMD module format.

The Asynchronous Module Definition (AMD) API specifies a mechanism for defining modules such that the module and its dependencies can be asynchronously loaded.

Latest on 0.2.9

  • DefineJS now allows passing an actual es6 function generator right to the promise chain:
define.Promise.resolve(jQuery)
  .then(function * ($) {
    var db = {},
      AsyncDB = yield require('asyncdb'),
      pkg = yield $.getJSON('package.json');
 
    //Not relevant but it is worth noting that AsyncDB is an async local data storage based on IndexedDB
    AsyncDB.new('packages', db);
 
    var pageContent = yield $.get(pkg.repository.url);
 
    $(pageContent).appendTo('.container');
 
    var versionEl = $('.container span.version');
 
    return yield db.packages.insert({
      name: pkg.name,
      version: pkg.version
    });
  })
  .then(function (packageId) {
    //a totally private and dedicated scope which has all it needs a packageId
    //...
  });
  • DefineJS offers a new helper function for generators:
function * myFunc(collection){
  var data = yield getData(collection);
 
  //Do something
 
  return data.app; 
}

Now to call the function and start the chain you could easily do:

myFunc.go('users')
  .then(function(app){
    //The app object comes from the myFunc's return value
 
  })

Compared to a regular function .go() is similar To a fn.call() and to pass an array as the list of desired arguments similar to fn.apply() DefineJS offers another function named: goWith():

myFunc.goWith(['users'])
  .then(function(app){
    //The app object comes from the myFunc's return value
 
  })

But if there is no need to pass any argument to the function:

myFunc
  .goThen(function(app){
    //The app object comes from the myFunc's return value
    
  })
  • DefineJS is not just an AMD module loader, it provides you with the luxury of setting up your application's asynchronous lifecycle without having to write down a huge require block, you could code using either of the new styles that DefineJS offers. If you don't like using IIFEs or if you are tired of the Pyramid of Doom when dealing with callbacks:
config.go()
  .then(firstPhase)
  .then(secondPhase)
  .then(finalPhase)
  .catch(lifecycleInterruption)
  .done(theEnd);
 
function * sameLifecycle() {
  var message;
  try {
    var packageInfo = yield config.go();
    var app = yield firstPhase.go(packageInfo);
    var shimModule2 = yield secondPhase.go(app);
    message = yield finalPhase.go(shimModule2);
  } catch (err) {
    message = yield lifecycleInterruption.go(err);
  }
  theEnd(message);
}

Take a thorough look at the two code block above. They both do the exact same thing without us needing to create IIFEs and using callbacks.

Features

Other than regular AMD module pattern, DefineJS also offers couple of nonstandard but usefull modular coding patterns. To make it more readable and getting to know the new features once they get released here we have top down list of DefineJS features list.

  • CommonJS/AMD Hybrid Format: This hybrid format allows to write modules with a CommonJS similar syntax.
  • ES6 generators: which allows to write asynchronous lazy loaded modules in a synchronous looking way of coding.
    • Open Discussion: This feature is still in its early days so that it needs more feedback from JavaScript community. There is an open issue (#9) to discuss the feedbacks, feel free to drop a line and bring up your ideas regarding this feature.
  • Promised Modules: Using the same AMD module style you can have privileged promise based modules.
    • Open Discussion: Rejection state of promised modules is still one of the open discussions, there is an open issue (#4) to discuss it, feel free to drop your comments.
  • use() vs require(): another nonstandard function called use() with a similar approach to the standard require() function which allows to have partial execution code blocks without having to use different main files.
  • AMD Module format

CommonJS/AMD Hybrid Format

This hybrid syntax allows to write modules with a new syntax similar to CommonJS. This feature is now possible thanks to the ES6 generators.

Let's imagine a CommonJS module like:

//app.js
var utils = require('utils'),
  $ = require('../vendor/jquery');
 
var app = {
  //...
};
 
module.exports = app;

The DefineJS alternative is:

//app.js
define(function* (exports, module) {
  var utils = yield require('utils'),
    $ = yield require('../vendor/jquery');
 
  var app = {
    //...
  };
 
  module.exports = app;
});

As mentioned the new syntax is similar to the CommonJS coding style, with two specific differences. First the yield keyword and the next is the define wrapper with a ES6 function generator.

ES6 generators

This library provides you with a the possiblity of using ES6 generators and the yield keyword along with promises. You can use yield keyword to load your desired dependencies without getting through the callback hell.

//app.js
define(function* () {
  var _,
    app;
  
  if(loadashIsNeeded){
    _ = yield require('../vendor/lodash');
  } else {
    _ = yield require('../vendor/underscore');
  }
  
  app = {
    //...
  };
  
  return app;
});

Then in order to require this module, you could require it as a regular AMD module:

//main.js
require(['app'],
  function (app) {
    app.lunch();
  });

Or use the new require function like:

//main.js
require(function* () {
  var app = yield require('app');
  app.lunch();
});

Give that a try and let us know how it feels to implement an asynchronous module definition with a fully synchronous looking code.

Promised Modules

Using the same AMD module style you can have privileged promise based modules. All you need to do is just returning a promise in your modules, to make them promised modules. To see how it works, just check out the simple-promised-module example in the examples folder.

In this example we have a promised module named: promisedModule.js which is responsible to wait for a specific global variable, then serve it as part of module's promised value.

//promisedModule.js
define([ /*'dependency'*/ ], function ( /*dependency*/ ) {
 
  return new Promise(function (fulfill, reject) {
    //Here you expect to have a global variable named: myApp after 2 seconds
    //otherwise your module definition gets rejected
 
    setTimeout(function () {
      if (window.myApp !== undefined) {
 
        //fulfill when succeeded and pass the fulfillment value
        fulfill({
          app: window.myApp,
          log: 'This is just a sample promised object to serve as a promised module!'
        });
 
      } else {
 
        //reject in case of error or unsuccessful operations
        reject(new Error('No global myApp object found!!'));
      }
 
    }, 2000);
  });
 
});

Now you could easily require it, or add it as a dependency. What will happen is, it waits for your promise to get resolved then you will have the promised module object.

//main.js
require(['promisedModule'],
  function(promisedModule){
    console.log(promisedModule.log);//=>This is just a sample promised object!
    console.log(promisedModule.app);
  });

Note: we are still discussing about the proper way of handling the rejected state of a promised module. Any feedback or proposal is really appreciated.

use() vs require()

You can also have the same modules flow using a new offered syntax by DefineJS:

use(['dependency1', 'dependency2'])
  .then(function(dependency1, dependency2){
    //...
    return dependency1.util;
  })
  .then(function(util){
    //...
    //use util object if it has any useful functionality
    return util.map([/*...*/]);
  })
  .catch(function(e){
    //in case of having a rejected promised module or any async error
    console.error(e);
  });

AMD Module format

You can define and require your modules using the regular AMD format:

myGlobal.define([/*'dependency'*/], function(/*dependency*/]){
  function moduleFunction(){
    //...
  }
  return moduleFunction;
});
myGlobal.require([/*'moduleName'*/], function(/*moduleName*/]){
  
});

Global define and require functions

To use AMD module definition functions(define and require) like what you have seen so far, as global functions, you could simply add the script tag like:

<script global="window" src="define.js"></script>

Then it could load any standard amd modules in your page.

Installation

Install with Bower:

bower install --save definejs

The component can be used as a Common JS module, an AMD module, or a global.

API

To use DefineJS in your JavaScript code, you could simply add it as a script tag:

<script src="define.js"></script>

Then you should call the definejs function to expose the amd modules functions to your desired global object:

definejs(myGlobal);

The easier way of achieving this, is to pass your desired global object to the global attribute of the script tag:

<script global="myGlobal" src="define.js"></script>

Or in case you need define and require functions as globals:

<script global="window" src="define.js"></script>

Based on the known JavaScript bad practice when defining global objects, this way with explicitly assigning the AMD functions to a specific global object or to the global scope you could be aware of the state of your global scope and also the possible consequences.

Note:

  • define.promise.js: To be able to use the latest DefineJS feature, which allows to use ES6 generators, instead of define.js you should add define.promise.js to your page:

    <script global="window" src="define.promise.js"></script>

    The other parts are exactly the same.

  • Promises polyfill: DefineJS doesn't reinvent the wheel but provides you with the official Promises polyfills from promisejs.org. You could find the latest version of the polyfill in the polyfills folder.

Testing

Install Node (comes with npm) and Bower.

From the repo root, install the project's development dependencies:

npm install
bower install

Testing relies on the Karma test-runner. If you'd like to use Karma to automatically watch and re-run the test file during development, it's easiest to globally install Karma and run it from the CLI.

npm install -g karma
karma start

To run the tests in Firefox, just once, as CI would:

npm test

Browser support

  • Google Chrome (latest)
  • Opera (latest)
  • Firefox 4+
  • Safari 5+
  • Internet Explorer 8+

Readme

Keywords

none

Package Sidebar

Install

npm i definejs

Weekly Downloads

0

Version

0.2.9

License

none

Last publish

Collaborators

  • mehranhatami