Skip to content

Commit

Permalink
docs, test: clarify how to hook an module sub-path using .cjs extension
Browse files Browse the repository at this point in the history
  • Loading branch information
trentm committed May 6, 2024
1 parent 826dbde commit ea5f6fd
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 13 deletions.
48 changes: 35 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,27 +43,49 @@ When called a `hook` object is returned.

Arguments:

- `modules` <string[]> An optional array of module names to limit which modules
trigger a call of the `onrequire` callback. If specified, this must be the
first argument. Both regular modules (e.g. `react-dom`) and
sub-modules (e.g. `react-dom/server`) can be specified in the array.
- `options` <Object> An optional object containing fields that change when the
`onrequire` callback is called. If specified, this must be the second
argument.
- `options.internals` <boolean> Specifies whether `onrequire` should be called
when module-internal files are loaded; defaults to `false`.
- `onrequire` <Function> The function to call when a module is required.
- `modules` {string[]} An optional array of module names or normalized module
sub-paths to limit which `require(...)` calls will trigger a call of the
`onrequire` callback. If specified, this must be the first argument. There
are a number of forms these entries can take:

- A package name, e.g., `express` or `@fastify/busboy`.
- A package [entry-point](https://nodejs.org/api/packages.html#package-entry-points),
as listed in the "exports" entry in a package's "package.json" file, e.g.
`some-package/entry-point`.
- A package sub-module.
E.g., `express/lib/request` will hook
`.../node_modules/express/lib/request.js` and `express/lib/router` will hook
`.../node_modules/express/lib/router/index.js`. (Note: To hook an internal
package file using the `.cjs` extension you must specify the extension in
the `modules` entry. E.g. `@langchain/core/dist/callbacks/manager.cjs` is
required to hook
`.../node_modules/@langchain/core/dist/callbacks/manager.cjs`.
This is because []`.cjs` is not handled specially by `require()` the way
`.js` is](https://nodejs.org/api/modules.html#file-modules).)
- A package sub-path, *if `options.internals === true`*. Using the `internals`
option allows hooking raw paths inside a package. The hook arguments for
these paths **include the file extension**. E.g.,
`new Hook(['@redis/client/dist/lib/client/index.js'], {internals: true}, ...`
will hook `.../node_modules/@redis/client/dist/lib/client/index.js`.

- `options` {Object} An optional object to configure Hook behaviour. If
specified, this must be the second argument.

- `options.internals` {boolean} Specifies whether `onrequire` should be called
when any module-internal files are loaded; defaults to `false`.

- `onrequire` {Function} The function to call when a module is required.

The `onrequire` callback will be called the first time a module is
required. The function is called with three arguments:

- `exports` <Object> The value of the `module.exports` property that would
- `exports` {Object} The value of the `module.exports` property that would
normally be exposed by the required module.
- `name` <string> The name of the module being required. If `options.internals`
- `name` {string} The name of the module being required. If `options.internals`
was set to `true`, the path of module-internal files that are loaded
(relative to `basedir`) will be appended to the module name, separated by
`path.sep`.
- `basedir` <string> The directory where the module is located, or `undefined`
- `basedir` {string} The directory where the module is located, or `undefined`
for core modules.

Return the value you want the module to expose (normally the `exports`
Expand Down
85 changes: 85 additions & 0 deletions test/cjs-sub-module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
'use strict'

// This tests that sub-module files using the `.cjs` extension are *not*
// hookable via a normalized module path. Instead one must use the .cjs
// extension on the hook arg.
//
// E.g., a Hook arg of `cjs-sub-module/foo` will **not** hook
// `./node_modules/cjs-sub-module/foo.cjs`. This is different compared to `.js`
// file extension usage. The difference is that Node.js's `require()` treats
// `.js` and `.cjs` differently.
// See https://nodejs.org/api/modules.html#file-modules

const test = require('tape')

const { Hook } = require('../')

test('require("cjs-sub-module/foo") does NOT hook cjs-sub-module/foo.cjs', function (t) {
const hook = new Hook(['cjs-sub-module/foo'], function (exports) {
t.fail('should not get here')
return exports
})

t.equal(require('./node_modules/cjs-sub-module'), 'cjs-sub-module/index.js')
t.equal(require('./node_modules/cjs-sub-module/foo.cjs'), 'cjs-sub-module/foo.cjs')

try {
require('./node_modules/cjs-sub-module/foo')
t.fail('the previous require should throw')
} catch (err) {
t.ok(/Cannot find module/.test(err.message), 'got expected exception')
}

hook.unhook()
t.end()
})

test('require("cjs-sub-module/bar") does NOT hook cjs-sub-module/bar/index.cjs', function (t) {
const hook = new Hook(['cjs-sub-module/bar'], function (exports) {
t.fail('should not get here')
return exports
})

t.equal(require('./node_modules/cjs-sub-module'), 'cjs-sub-module/index.js')
t.equal(require('./node_modules/cjs-sub-module/bar/index.cjs'), 'cjs-sub-module/bar/index.cjs')

try {
require('./node_modules/cjs-sub-module/bar')
t.fail('the previous require should throw')
} catch (err) {
t.ok(/Cannot find module/.test(err.message), 'got expected exception')
}

hook.unhook()
t.end()
})

test('require("cjs-sub-module/foo.cjs") DOES hook cjs-sub-module/foo.cjs', function (t) {
const hookedNames = []
const hook = new Hook(['cjs-sub-module/foo.cjs'], function (exports, name) {
hookedNames.push(name)
return exports
})

t.equal(require('./node_modules/cjs-sub-module'), 'cjs-sub-module/index.js')
t.equal(require('./node_modules/cjs-sub-module/foo.cjs'), 'cjs-sub-module/foo.cjs')
t.deepEqual(hookedNames, ['cjs-sub-module/foo.cjs'])

hook.unhook()
t.end()
})

test('require("cjs-sub-module/bar/index.cjs") DOES hook cjs-sub-module/bar/index.cjs', function (t) {
const hookedNames = []
const hook = new Hook(['cjs-sub-module/bar/index.cjs'], function (exports, name) {
hookedNames.push(name)
return exports
})

t.equal(require('./node_modules/cjs-sub-module'), 'cjs-sub-module/index.js')
t.equal(require('./node_modules/cjs-sub-module/bar/index.cjs'), 'cjs-sub-module/bar/index.cjs')
t.deepEqual(hookedNames, ['cjs-sub-module/bar/index.cjs'])

hook.unhook()
t.end()
})
1 change: 1 addition & 0 deletions test/node_modules/cjs-sub-module/bar/index.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/node_modules/cjs-sub-module/foo.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/node_modules/cjs-sub-module/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit ea5f6fd

Please sign in to comment.