Security News
Input Validation Vulnerabilities Dominate MITRE's 2024 CWE Top 25 List
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
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!
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.
npm install r42
Let's code lib/toto.js
:
// path & fs are examples to show you how to load node modules
// _ will contain lodash (see next section to see why)
define(function(path, fs, _, dep1, dep2) {
/* Your code goes here */
// You can export anything, a scalar, a function, an object, an array...
// Objects & functions should be preferred
return /* what you exports goes here */;
});
var r42 = require('r42');
var path = require('path');
var context = r42.config({
// Sets the root directory in which modules are looked for
baseDir: path.join(__dirname, 'lib'),
// Allows to map paths / module names to something else
paths: {
// _ will load lodash
_: 'lodash',
// Target paths are all relative to baseDir.
// Here "library" will try to load "baseDir + '../vendor/library'"
library: '../vendor/library',
// Replacement in paths are also possible: here all modules named
// 'sub/.../truc' will be looked for into the ../sub folder
sub: '../sub',
// Alias can also be used in configuration but they HAVE TO be declared
// BEFORE being used in the object
shMod: 'sh/module', // refers to 'sh/module'
sh: 'shortcut',
shMod2: 'sh/module2', // refers to 'shortcut/module2'
},
});
// Let's get started
context.inject(function(toto) {
// you can use your dependency here
});
Module names can handle any characters that are allowed in a path since they are derived from file names except dot (.
) and exclamation mark (!
) which are reserved by r42.
We also strongly advise you not to use characters forbidden in Windows file system (eg : < > " \ | ? *
). For a full list of those, you can refer to this MSDN page.
A good (best) practice would be to only use english alphanumeric characters [a-zA-Z0-9]
without any special character. You might include _
if you are a snake case adept but it's more C like than JS like. I would rather recommend using camel case because it's going to look nicer in your code.
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) {
// here test will be resolved to module 'sub/folder/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(/*! sub/folder/test */ test) {
// here test will be resolved to module 'sub/folder/test'
});
Note : spaces are optional in r42 comments.
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, /*! $sub/sideValue */ sideVal) {
// superSub refers to 'module/super/sub'
// $sideFn refers to module/sideFn
// sideVal refers to $sub/sideValue
});
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) {
// Here $folder will load folder/index.js
});
r42 supports requiring only part of a module by using the dot (.
) notation just like in this example:
define(function(/*!myModule*/ myModule, /*!myModule.MyObject*/ MyObject,
/*!myModule.tree.TreeNode*/ TreeNode) {
// here myModule.MyObject === MyObject
// and myModule.tree.TreeNode === TreeNode
});
As you can also see in the above example, the dot notation is not restricted to a single level in the module. You can look for nested properties as deep as you want.
Please note that there is no array notation allowing you to access properties in an array using the [] operator.
Since the dot notation is reserved to load subproperties (see above), you need to alias modules in your r42 config.pahs to load such modules. Here is an example:
var r42 = require('r42');
var context = r42.config({
baseDir: __dirname,
// Allows to map paths / module names to something else
paths: {
myComplexModule: 'my.complex.module'
},
});
// Let's get started
context.inject(function(myComplexModule) {
// here myComplexModule will be module 'my.complex.module'
// and not the 'complex.module' subproperty of module 'my'
});
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) {
// here b might be empty if it was loaded before a, so don't use it
return {
aFn: function() {
// here b should be fully usable
b.bFn();
},
};
});
In a module b.js
:
define(function(a) {
// here a might be empty if it was loaded before b, so don't use it
return {
bFn: function() {
// here a should be fully usable
a.bFn();
},
};
});
r42 also supports the Require.js exports
notation. It is very useful when you might want to replace an exported function of a module by another to spy on it (in tests sequences for example). It might also be used to support circular dependencies. When you do not use exports
, r42 copies everything you return into the object it presented to other dependencies. This is slower and might cause bugs that exports
would not create.
Here is an example of a compatible module for exporting values using exports
:
define(function(exports) {
exports.fnA = function() {
// ...
};
// ...
return exports; // optional
});
exports is also accessible without injection using this
:
define(function(exports) {
this.fnA = function() {
// ...
};
// ...
});
Exporting primitives is usually not possible to do if you also need this module to work in a circular dependency model. To circumvent this, the easiest solution is to export the primitive in a subproperty of your module like so:
define(function(circularDep) {
// here instead of returning the function, I export it using the object way…
this.myPrimitive = function() {
// use circularDep in some way
};
})
This is working well in most cases. The only thing to ensure is not to use the value in the module definition itself but only in exported codebase:
define(function(myPrimitiveModule) {
myPrimitiveModule.myPrimitive(); // Might fail!
this.someFn = function() {
myPrimitiveModule.myPrimitive(); // OK
};
})
Returning primitives is dangerous but is easier to use. Thus, sometimes, you might prefer to do it. r42 will protect you if you tell him what you are doing explicitely:
define(function(r42, someDep) {
// we are explicitely exporting this module as a primitive
return r42.exportsPrimitive(function() {
// use someDep in some way
});
})
In this case, if someDep becomes circular at any point, r42 will throw an error preventing us to do so. This is the recommended way to export primives.
Important
Do not use the dot notation when accessing a subproperty of a circular dependency This is basically the same as using the primive in the module definition. Currently it will fail silently. A safeguard will be added in the future.
define(function(r42, /*! myPrimitiveModule.myPrimitive */ myPrimitive) {
// here myPrimitive can be undefined depending on the module load order:
// this is bad so don't do it
// See context.dumpDeps() below to find out what your dependency
// tree look like
})
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(/*! json!$config */ config) {
// Here "config" contains ./config.json parsed into a JS structure
});
With the require loader:
define(function(/*! require!$externalModule */ externalModule) {
// Here "externalModule" contains the value returned by calling require with
// ./externalModule
});
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',
// whatever
},
isolate: [
'_' // you can use aliases or full module names here (npm or local)
'json!package', // you can also refer to modules loaded by plugins
(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.
// BAD order
r42
.config({
isolate: ['i18next'],
})
.inject(function(depNeedingI18next, i18next) {
// this is bad because here i18next will be loaded in require.cache before
// r42 can load i18next thus it will already be in cache and there will be
// nothing to clean
});
// GOOD order
r42
.config({
isolate: ['i18next'],
})
.inject(function(i18next, depNeedingI18next) {
// this is good because r42 will load i18next and store it in its internal cache
// then it will clean r42 from require's cache allowing your dependency to find
// a clean cache and to load it from scratch again
});
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; // true
a instanceof isolated2.MyObject; // false
// this is so because isolated2.MyObject is a different function containing the
// exact same code than isolated.MyObject, so they are not stricly equal (===)
isolated !== isolated2 // true
isolated.MyObject !== isolated.MyObject // true
});
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) {
// This will load lodash in variable _.
var _ = r42.inject('lodash');
// Same thing for the variable alias but using the replacer argument.
var alias = r42.inject({ alias: 'lodash' }, 'alias');
// An example with multiple load at once
// Here modules[0] will resolve to require('util')
// and modules[1] to require('net')
var modules = r42.inject(['util', 'net']);
// You can also use the replacer argument with an array:
// this example will load the same modules as the previous one
modules = r42.inject({a: 'util', b: 'net'}, ['a', 'b']);
});
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 = /* create here as you want a r42 context with normal paths */
var subContext = context.createSub(
"test", // this argument can be omitted if process.env.NODE_ENV="test"
{
paths: {
moduleToMockup: 'mockupModule',
}
}
);
// now subContext can be used just as context and inherits its configuration
subContext.inject(/* inject as usual */);
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) {
// moduleDef contains information about the module that should be loaded
// parent contains information about the module that required moduleDef
// mc is the context object, it handles the modules' cache
// mc.$baseDir is the root path of the project
// moduleDef.name contains the path to the module from baseDir
// (without any extension)
var filePath = path.join(mc.$baseDir, moduleDef.name + '.yaml');
try {
// The loading should be synchronous
var yaml = fs.readFileSync(filePath, 'utf8');
yaml = myYamlModule.parse(yaml);
// Init the module into the context
mc.initModule(moduleDef, yaml);
} catch (e) {
// <module>.fullName contains the plugin's name + ! + the module's name
e.message = '[r42:yaml] Cannot load module ' + moduleDef.fullName +
' (required by module ' + parent.fullName + '): ' + e.message;
throw e;
}
});
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 notWARNING: 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({
// Your config HERE
});
// Let's get started
context.inject(function(/* Your dependencies */) {
// Dump dependencies with colors using process.stdout.print
context.dumpDeps();
// This verion will return you the string without any colors and without printing it :
var depstr = context.dumpDeps({
colors: false, // Do not use term colors in the output string
print: false, // Do not print the string on stdout
});
// Now you can do whatever you want with the string, like send it over network...
});
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,
/* Other configuration properties */
});
context.inject(function(/* Your dependencies */) {
module.exports = {
// Whatever
};
});
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).
exports
(or modules requiring themselves) as circular dependencies in context.dumpDeps()
r42.exportsPrimitive
(path to deprecate most of the return syntax)return
optional in modules (prepare for deprecation)this
in module definitions as an alias to exports
.
) notation to load only sub properties of a moduleexports
dependencypaths
configuration propertyisolate
configuration propertyrequire
configuration property (see Using r42 in libraries in the documentation)path
configuration property to contain plugin syntax (e.g: json!package.json
is valid)FAQs
Dependency injection done right.
The npm package r42 receives a total of 6 weekly downloads. As such, r42 popularity was classified as not popular.
We found that r42 demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.
Research
Security News
A threat actor's playbook for exploiting the npm ecosystem was exposed on the dark web, detailing how to build a blockchain-powered botnet.