February 16, 2014

Requiring Local Parent Modules from Inside a Module

Part of making NodeBB extensible is programming a plugin system, similar to CMS platforms like WordPress and Drupal. The plugins themselves enhance the base feature set of NodeBB and allow the community to accelerate development of features that they deem necessary, not the core developers.

Prior to today, the plugin system was fairly unremarkable. It loaded plugins from the /plugins folder1 or via node modules installed via npm, and exposed system functionality via a set of hooks. This meant that plugins themselves were essentially standalone, and that all logic was contained within the module itself.

In the case of NodeBB’s "mentions" plugin, we were using the following hooks:

  • filter:post.parse
  • action:post.save

Problem

We needed the following functionality in addition to what was provided:

  • If a mention was detected (e.g. "@julian"), only make that mention an anchor if the user exists in the NodeBB
  • If a user mentions another user, send them a notification via the built-in Notifications class.

The problem with these two features was that we required the User and Notifications modules, respectively2.

My first attempt was simply require-ing the modules like any other module:

var	Topics = require('../../src/topics');

However, this is undesirable behaviour because it may be bad form to refer to Objects outside of the module root. In addition, we ran into an issue while using npm link3.

Solution

To resolve this, we needed an elegant (or if not elegant, then a "node-like") way to utilise core NodeBB functionality while preserving npm link functionality.

The NodeJS documentation contained a possible solution:

module.require(id)

  • id String
  • Return: Object module.exports from the resolved module

The module.require method provides a way to load a module as if require() was called from the original module.

Note that in order to do this, you must get a reference to the module object. Since require() returns the module.exports, and the module is typically only available within a specific module’s code, it must be explicitly exported in order to be used.

The description is a bit confusing… how is it different from calling require() on my own?

The second paragraph only made sense after you realize that "you must get a reference to the module object" means that module itself needs to be a reference to another instance of module.exports. For example, if my app had a foo module, and I wanted to call the bar module required by foo, you could write…

var	foo = require('foo'), bar = foo.require('bar');

Luckily for us, this works in reverse too, if we combine it with module.parent! From inside our module:

var Topics = module.parent.require('./topics'), User = module.parent.require('./user'), Notifications = module.parent.require('./notifications');

In our case, because every NodeBB plugin is always called by plugin.js, we can safely assume that module.parent points to module.exports provided by plugin.js.


1 Loading local modules via the /plugins folder is deprecated as of v0.0.7
2 nodebb-plugin-mentions actually uses three NodeBB classes, User, Topics, and Notifications.
3 The gist of it is – when npm link-ed, the relative paths used (../../) point to /usr/lib/, not the current working directory of the node application!

© 2014 – 2023 NodeBB, Inc. — Made in Canada.