Skip to content

Commit

Permalink
module: add submodules builtins in addBuiltinLibsToObject
Browse files Browse the repository at this point in the history
  • Loading branch information
Mesteery committed Sep 25, 2021
1 parent fd86dad commit dfc66e0
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 44 deletions.
7 changes: 7 additions & 0 deletions doc/api/repl.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@ global or scoped variable, the input `fs` will be evaluated on-demand as
> fs.createReadStream('./some/file');
```

Node.js core submodules are exposed in parent module object,
for example, `stream/consumers` would be accessible in `stream.consumers`.

```console
> stream.consumers.text(process.stdin);
```

#### Global uncaught exceptions
<!-- YAML
changes:
Expand Down
96 changes: 53 additions & 43 deletions lib/internal/modules/cjs/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const {
SafeMap,
SafeSet,
StringPrototypeCharCodeAt,
StringPrototypeIncludes,
StringPrototypeSplit,
StringPrototypeSlice,
StringPrototypeStartsWith,
} = primordials;
Expand Down Expand Up @@ -145,52 +145,62 @@ function addBuiltinLibsToObject(object, dummyModuleName) {
const { builtinModules } = Module;

// To require built-in modules in user-land and ignore modules whose
// `canBeRequiredByUsers` is false. So we create a dummy module object and not
// use `require()` directly.
// `canBeRequiredByUsers` is false. So we create a dummy module object and
// not use `require()` directly.
const dummyModule = new Module(dummyModuleName);

ArrayPrototypeForEach(builtinModules, (name) => {
// Neither add underscored modules, nor ones that contain slashes (e.g.,
// 'fs/promises') or ones that are already defined.
if (StringPrototypeStartsWith(name, '_') ||
StringPrototypeIncludes(name, '/') ||
ObjectPrototypeHasOwnProperty(object, name)) {
return;
}
// Goals of this mechanism are:
// - Lazy loading of built-in modules
// - Having all built-in modules available as non-enumerable properties
// - Allowing the user to re-assign these variables as if there were no
// pre-existing globals with the same name.

const setReal = (val) => {
// Deleting the property before re-assigning it disables the
// getter/setter mechanism.
delete object[name];
object[name] = val;
};
// Goals of this mechanism are:
// - Lazy loading of built-in modules or submodules
// - Having all built-in modules or submodules available as
// non-enumerable properties
// - Allowing the user to re-assign these variables as if there were no
// pre-existing globals with the same name.

function attachModulesWrapper(object, parent, modules) {
ArrayPrototypeForEach(modules, (mod) => {
const name = parent ? StringPrototypeSplit(mod, '/')[1] : mod;

// Neither add underscored modules/submodules or ones that
// are already defined.
if (StringPrototypeStartsWith(name, '_') ||
ObjectPrototypeHasOwnProperty(object, name)) {
return;
}

ObjectDefineProperty(object, name, {
get: () => {
const lib = dummyModule.require(name);

// Disable the current getter/setter and set up a new
// non-enumerable property.
delete object[name];
ObjectDefineProperty(object, name, {
get: () => lib,
set: setReal,
configurable: true,
enumerable: false
});

return lib;
},
set: setReal,
configurable: true,
enumerable: false
ObjectDefineProperty(object, name, {
get() {
const lib = dummyModule.require(mod);

const submodules = builtinModules
.filter((module) => module.startsWith(`${mod}/`));
if (submodules.length > 0)
attachModulesWrapper(lib, mod, submodules);

// Disable the current getter/setter and set up a new
// non-enumerable property.
delete object[name];
ObjectDefineProperty(object, name, {
value: lib,
writable: true,
configurable: true,
enumerable: false,
});
return lib;
},

set(val) {
// Deleting the property before re-assigning it disables the
// getter/setter mechanism.
delete object[name];
object[name] = val;
},
configurable: true,
enumerable: false,
});
});
});
}

attachModulesWrapper(object, '', builtinModules);
}

function normalizeReferrerURL(referrer) {
Expand Down
19 changes: 18 additions & 1 deletion test/parallel/test-repl-autolibs.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ function test1() {
putIn.write = function(data) {
gotWrite = true;
if (data.length) {

// Inspect output matches repl output
assert.strictEqual(data,
`${util.inspect(require('fs'), null, 2, false)}\n`);
Expand All @@ -59,6 +58,7 @@ function test2() {
assert.strictEqual(data, '{}\n');
// Original value wasn't overwritten
assert.strictEqual(val, global.url);
test3();
}
};
const val = {};
Expand All @@ -68,3 +68,20 @@ function test2() {
putIn.run(['url']);
assert(gotWrite);
}

function test3() {
let gotWrite = false;
putIn.write = function(data) {
gotWrite = true;
if (data.length) {
// Inspect output matches repl output
assert.strictEqual(data,
`${util.inspect(require('stream/consumers'), null, 2, false)}\n`);
// Globally added lib matches required lib
assert.strictEqual(global.stream.consumers, require('stream/consumers'));
}
};
assert(!gotWrite);
putIn.run(['stream.consumers']);
assert(gotWrite);
}

0 comments on commit dfc66e0

Please sign in to comment.