r42
Mix in node's require, some angular dependency injection look&feel and the ease of use of RequireJS and what do you get?
Dependency injection done right for Node.js!
Important notes
Since 0.6.0 module's isolation at load time has been reworked to be optional and deactivated by default instead of enforced on all loaded modules. Please be careful when upgrading.
In general, be careful when upgrading before 1.0.0 is released. Carefully review changelog. Minor changes (last digit in version number) can be considered safe though.
Getting started
Installation
npm install r42
Your first module
Let's code lib/toto.js
:
define(function (path, fs, _, dep1, dep2) {
return ;
});
Configuring & lauching your project
var r42 = require('r42');
var path = require('path');
var context = r42.config({
baseDir: path.join(__dirname, 'lib'),
paths: {
_: 'lodash',
library: '../vendor/library',
sub: '../sub',
shMod: 'sh/module',
sh: 'shortcut',
shMod2: 'sh/module2',
},
});
context.inject(function (toto) {
});
More complex dependencies
Module in subfolders
If you want to resolve complex names in subdirectories, you can use the optional replacer
argument of the define function. Here is an example:
define({
test: 'sub/folder/test',
}, function (test) {
});
The object maps an argument's name to the real module name locally. The argument name will be replaced by the given module name before injection happens.
You can also use special r42 comments (looking like /*! module */
) before your argument names:
define(function ( test) {
});
Note : spaces are optional in r42 comments.
Modules in the same folder
Sometimes, it is a pain to refer to a module in the same folder as you are right now. r42 allows for a fine way to do so.
Using $
to prefix your variable's name will automatically cause r42 to replace it by your current module's "path". It also works to prefix files in the replacer
object.
In a module module/toto.js
:
define({
superSub: '$super/sub',
}, function (superSub, $sideFn, sideVal) {
});
Special case : index.js in a subfolder
You can refer to an index module using only the folder's name just like so:
In file folder/index.js
:
define(function () {
return {
answer: 42
};
});
And in a file at the same level as folder:
define(function ($folder) {
});
Circular dependencies
Those are working "automatically" but you NEED to exports barebone objects on both modules for it to work.
Here is an example:
In a module a.js
:
define(function (b) {
return {
aFn: function () {
b.bFn();
},
};
});
In a module b.js
:
define(function (a) {
return {
bFn: function () {
a.bFn();
},
};
});
Other load mechanism
r42 allows you to create your own loading modules & to load modules using a different policy than its own. By default, a require load mechanism & a json load mechanism are included by default in r42.
To use a different load mechanism than the default one, you need to prefix its name by plugin!
(eg: require!module
or json!module
). This implies that you have to use the replacer
argument or r42 comments to load a plugin using a specific loader.
Examples:
With the json loader:
define(function ( config) {
});
With the require loader:
define(function ( externalModule) {
});
Isolation
Sometimes some modules (often npm libraries) are singletons. That can cause a lot of problems when they are used by both you and one of your dependencies because you might not want to use the same configuration on it. Because code will be shared as an optimization by npm, it can cause headaches to solve this situation.
r42 provide a way to partially negate this problem. It is called isolation
. This mechanism allows you to ask r42 to clean the module and its dependencies from the require's cache after loading them. Like that, any other require on them will create a brand new RAM instance of the library.
To use isolation, you just have to fill properly the isolate
configuration variable like so:
r42.config({
paths: {
_: 'lodash',
},
isolate: [
'_'
'json!package',
(your plugin needs to support isolation)
]
});
Because your dependencies are probably not properly cleaning behind them singletons (everyone is not using r42 ^^), to ensure no problem arise, you should also make certain your isolated modules are loaded before the dependencies that use them.
Since r42 is synchronous, you can easily find out in which order a module is loaded.
r42
.config({
isolate: ['i18next'],
})
.inject(function(depNeedingI18next, i18next) {
});
r42
.config({
isolate: ['i18next'],
})
.inject(function(i18next, depNeedingI18next) {
});
As a special note: open bugs on the dependencies that do not clean singletons asking them to do so. It will allow you to never again think about the correct order to load modules. This should not be important (and is not in a full r42 environment properly configured).
Important note: if you isolate a module, instanceof
and such keywords won't work between instances of each isolated module (which might be good or bad depending on your use cases) and this might lead to strange bugs. Use with caution. Example :
r42.config({}).inject(function(isolated) {
var isolated2 = require('isolated');
var a = new isolated.MyObject();
a instanceof isolated.MyObject;
a instanceof isolated2.MyObject;
isolated !== isolated2
isolated.MyObject !== isolated.MyObject
});
Special APIs
Loading modules dynamically
You can also use r42.inject to load modules dynamically. In this case, provide a module name or module list and optionally a replacer
as usual. Here is what it could look like:
In module dynamic.js
define(function (r42) {
var _ = r42.inject('lodash');
var alias = r42.inject({ alias: 'lodash' }, 'alias');
var modules = r42.inject(['util', 'net']);
modules = r42.inject({a: 'util', b: 'net'}, ['a', 'b']);
});
Testing API
There is a test API that is intended to help writing tests & speed up tests by preventing a module to be reloaded more than necessary. This API is accessible when the NODE_ENV
environment variable is set to "test"
or by passing "test" as a first argument to the createSub
method as demonstrated below:
var context =
var subContext = context.createSub(
"test",
{
paths: {
moduleToMockup: 'mockupModule',
}
}
);
subContext.inject();
Writing your own loader plugin
You can write other loader plugins and register them to r42 to use them after that in your codebase. Here is how to do so:
var r42 = require('r42');
var path = require('path');
var fs = require('fs');
var myYamlModule = require('myYamlModule');
r42.registerLoader('yaml', function (moduleDef, parent, mc) {
var filePath = path.join(mc.$baseDir, moduleDef.name + '.yaml');
try {
var yaml = fs.readFileSync(filePath, 'utf8');
yaml = myYamlModule.parse(yaml);
mc.initModule(moduleDef, yaml);
} catch (e) {
e.message = '[r42:yaml] Cannot load module ' + moduleDef.fullName +
' (required by module ' + parent.fullName + '): ' + e.message;
throw e;
}
});
Printing dependencies
This function might be useful to gather information about your module & to debug dependencies problems. It takes an options
parameter which is an object that can take the following two options:
colors
(defaults to true
): whether to colorize the output or notprint
(defaults to true
): whether to print to stdout automatically the result or not
WARNING: The colors
option requires that you install chalk
(version >=0.4.0) as a dependency of your project.
It returns a string containing the dependencies information.
var r42 = require('r42');
var context = r42.config({
});
context.inject(function () {
context.dumpDeps();
var depstr = context.dumpDeps({
colors: false,
print: false,
});
});
Using r42 in libraries
Since version 0.0.21, r42 has been rewritten to be completely synchronous. This means that you can use it easily, even to create library packages. Here is a simple example of what your main library file might look like :
var r42 = require('r42');
var context = r42.config({
require: require,
});
context.inject(function () {
module.exports = {
};
});
Since your library uses r42, you have to take into account the fact that a module depending on your code also uses it. If the versions required by your module and its dependant are compatible, they will share the r42 codebase. This means that r42 will be installed in a parent node_modules
folder. If nothing is done, it will break require
in your r42. To prevent this from happening,
you HAVE TO add the require
configuration variable to your configuration (in the same way as in the previous example).
Changelog
0.6.0
- do not isolate modules by default
- add
isolate
configuration property
0.5.3
- Add
require
configuration property (see Using r42 in libraries in the documentation)
0.5.2
- Allow
path
configuration property to contain plugin syntax (e.g: json!package.json
is valid)
0.5.0 & 0.5.1
0.4.0
- Add plugin API
- Add plugins
- default (previous loading mechanism moved to a plugin)
- require (force use of require, no define expected in module)
- json (load JSON file - using require)
0.3.1
- Remove the beta channel warning that was forgotten
0.3.0
- Make r42 instantiable.
- Clean up related require's cache after loading a module with r42
0.2.2
- Continue work started with 0.2.1 to prepare for 0.3.0
0.2.1
- Change architecture to prepare for 0.3.0 that will change r42 API so that r42 si not a singleton anymore
0.2.0
- Colors dependency was dumped in favor of chalk that does not mess up String's prototype
- dumpDeps
- small bug fixes
- better readability