ti-commonjs

0.0.1 • Public • Published

ti-commonjs Appcelerator Titanium Built with Grunt Gittip

Node.js-style CommonJS in Appcelerator Titanium via Alloy. For full details on what exactly this means, check out Node.js's own documentation on its CommonJS implementation. In addition to this added functionality, ti-commonjs also eliminates all platform-specific disparities in Titanium's CommonJS implementation.

Requirements

It is distinctly possible that ti-commonjs will work with earlier versions of both Titanim and Alloy, but they are untested and unsupported.

Install NPM version

Assuming you're in your Alloy project's root folder (not the app folder):

npm install ti-commonjs --prefix ./app/lib

Aside from installing ti-commonjs, this will also add the alloy.jmk file (or modifications to existing alloy.jmk) necessary to post-process your generated runtime files. Read here for more details on alloy.jmk files.

Usage

absolute paths (relative to "Resources" folder)

require('/foo/bar');

relative paths

require('../../someModule');
require('./someModuleInCurrentFolder');
require('.././some/ridiculous/../../path');

loading from node_modules folder

Load modules installed via npm in Resources/node_modules. Full details here.

var _ = require('underscore');
require('ti-mocha');

You can also target specific files within those npm installations. For example, let's take should.js. While you can't require it directly in Titanium, due to its reliance on node.js libraries, you can reference the single-file Titanium-compatible version embedded in the installation. This tactic works with almost every npm distributed module that has a browser-compatible version.

var should = require('should'); // ERROR - missing node.js libraries
 
var should = require('should/should'); // WOOHOO! It works in Titanium.

folders as modules

If a folder contains a package.json, the path in its main property will be loaded as a module. Additionally, a module named index.js can be referenced just by its folder's name. The following example demonstrates both uses. Full details here.

/foo/package.json

{
    "main": "./lib/quux"
}

/app.js

// assuming the module "Resources/foo/lib/quux.js" exists...
var foo = require('/foo');
 
// assuming the module "Resources/bar/index.js" exists...
var bar = require('/bar');

loading JSON files

You can now load JSON files simply with require().

console.log(require('/package.json').main);
 
// prints "./lib/ti-commonjs.js"

require.resolve()

Get the full path resolved path to a module.

require.resolve('/foo/bar.js') === require.resolve('/.././foo/../foo/bar');

require.main

Every require function now has a reference to the main module (app.js). Full details here.

/foo/bar.js

require.main.id === '.' && require.main.filename === '/app.js'

load module with or without extensions

require('/foo') === require('/foo.js')

"module" object

Titanium's implementation gives limited access to the properties of the module object. With ti-commonjs.js you have full access to the following properties and functions. Full details here.

Use the Titanium require()

Just in case you still need to use the old require() from Titanium, it's still accessible via tirequire().

require('/foo') === tirequire('foo')

FAQ

Why is this so cool?

Because now you can start leveraging the power of node.js and npm package management in your Alloy apps.

$ npm install --prefix ./app/lib ti-mocha should underscore

alloy.js

var should = require('should/should'), // require the broswer-compatible version in should
    _ = require('underscore');
require('ti-mocha');
 
describe('ti-commonjs', function() {
    it('should work', function() {
        _.each([1,2,3], function(num) {
            num.should.equal(num);
        });
    });
});
mocha.run();

Should I use ti-commonjs.js?

cons

  • It will probably break your existing Titanium code. The primary reason for this is fundamental difference in specifying absolute paths with Titanium's require() vs. ti-commonjs.js. This example illustrates, assuming that a module exists at Resources/foo/bar.js:
// This is how it's done with Titanium's require()
require('foo/bar');
 
// and this is how its done with ti-commonjs.js
require('/foo/bar');

pros

  • This is the CommonJS implementation and require() usage that will be supported in Titanium 4.0 (Lovingly being referred to as Ti.Next). You can start future-proofing your apps now.
  • You get all of the great features listed in the Usage section.
  • You can install and distribute modules via npm! no more digging through github or Q&A posts.
  • Eliminates all platform-specific disparities in Titanium's CommonJS implementation.
  • It becomes much easier to port existing node.js modules to Titanium. Many you'll be able to use now without any modifications.
  • It is much easier for incoming node.js developers to start using Titanium with this more familiar CommonJS implementation.

How does it work?

ti-commonjs.js overides the existing Titanium require() to have node.js-style functionality. It sits directly on top of Titanium's existing module implementation so all module caching is preserved, no wheels are re-invented. It does this by invoking the main ti-commonjs function with the current __dirname then returns a curried function as the new require().

To truly make the usage seamless, though, your generated Javascript files need a CommonJS wrapper, much like is done in the underlying engine itself. The wrapper looks like this:

app.js

(function(_require,__dirname,__filename) {
    var require = _require('ti-commonjs')(__dirname);
 
    // your code..
 
})(require,'/','/app.js');

Why can't I just create a new require variable?

Because you'd be conflicting with the require already in the scope of every module.

var require = require('ti-commonjs'); // CONFLICT with global require

Why can't I just override require() in my app.js?

I should just be able to take advantage of Titanium's scoping with respect to the app.js file and have require() overridden everywhere, right? Well, you're right, but that's where the problem lies. The issue is that require() needs to be executed relative to the current file's directory when it comes to relative paths. This is further compounded by properties like require.paths. Globally overriding require(), though, will make all paths relative to Resources. Let me demonstrate.

app.js

require = require('ti-commonjs');
require('/1/2/3/threeModule')();

1/2/3/threeModule.js

module.exports = function() {
    // DISASTER! You'd think you were referencing '/1/2/3/../twoModule' here,
    // but because the relative directory was established in the app.js
    // you are instead referencing '/../twoModule'. This will end in a
    // runtime error.
    require('../twoModule')();
};

1/2/twoModule.js

module.exports = function() {
    console.log();
};

What are the caveats?

  • module.parent and module.children cannot be supported since the underlying Titanium require() provides no means to get them or assign them to a module. To be able to support this, a change would be required in Titanium. Fortunately, these are rarely used.
  • This implementation does not load modules with the .node extension, as those are for node.js compiled addon modules, which make no sense in the context of Titanium.
  • ti-commonjs.js does not load from global folders (i.e., $HOME/.node_modules), as they are not relevant to mobile app distributions.

Package Sidebar

Install

npm i ti-commonjs

Weekly Downloads

1

Version

0.0.1

License

MIT

Last publish

Collaborators

  • tonylukasavage
  • yuchi