Skip to content

Commit dfc66e0

Browse files
committed
module: add submodules builtins in addBuiltinLibsToObject
1 parent fd86dad commit dfc66e0

File tree

3 files changed

+78
-44
lines changed

3 files changed

+78
-44
lines changed

doc/api/repl.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,13 @@ global or scoped variable, the input `fs` will be evaluated on-demand as
147147
> fs.createReadStream('./some/file');
148148
```
149149

150+
Node.js core submodules are exposed in parent module object,
151+
for example, `stream/consumers` would be accessible in `stream.consumers`.
152+
153+
```console
154+
> stream.consumers.text(process.stdin);
155+
```
156+
150157
#### Global uncaught exceptions
151158
<!-- YAML
152159
changes:

lib/internal/modules/cjs/helpers.js

Lines changed: 53 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const {
99
SafeMap,
1010
SafeSet,
1111
StringPrototypeCharCodeAt,
12-
StringPrototypeIncludes,
12+
StringPrototypeSplit,
1313
StringPrototypeSlice,
1414
StringPrototypeStartsWith,
1515
} = primordials;
@@ -145,52 +145,62 @@ function addBuiltinLibsToObject(object, dummyModuleName) {
145145
const { builtinModules } = Module;
146146

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

152-
ArrayPrototypeForEach(builtinModules, (name) => {
153-
// Neither add underscored modules, nor ones that contain slashes (e.g.,
154-
// 'fs/promises') or ones that are already defined.
155-
if (StringPrototypeStartsWith(name, '_') ||
156-
StringPrototypeIncludes(name, '/') ||
157-
ObjectPrototypeHasOwnProperty(object, name)) {
158-
return;
159-
}
160-
// Goals of this mechanism are:
161-
// - Lazy loading of built-in modules
162-
// - Having all built-in modules available as non-enumerable properties
163-
// - Allowing the user to re-assign these variables as if there were no
164-
// pre-existing globals with the same name.
165-
166-
const setReal = (val) => {
167-
// Deleting the property before re-assigning it disables the
168-
// getter/setter mechanism.
169-
delete object[name];
170-
object[name] = val;
171-
};
152+
// Goals of this mechanism are:
153+
// - Lazy loading of built-in modules or submodules
154+
// - Having all built-in modules or submodules available as
155+
// non-enumerable properties
156+
// - Allowing the user to re-assign these variables as if there were no
157+
// pre-existing globals with the same name.
158+
159+
function attachModulesWrapper(object, parent, modules) {
160+
ArrayPrototypeForEach(modules, (mod) => {
161+
const name = parent ? StringPrototypeSplit(mod, '/')[1] : mod;
162+
163+
// Neither add underscored modules/submodules or ones that
164+
// are already defined.
165+
if (StringPrototypeStartsWith(name, '_') ||
166+
ObjectPrototypeHasOwnProperty(object, name)) {
167+
return;
168+
}
172169

173-
ObjectDefineProperty(object, name, {
174-
get: () => {
175-
const lib = dummyModule.require(name);
176-
177-
// Disable the current getter/setter and set up a new
178-
// non-enumerable property.
179-
delete object[name];
180-
ObjectDefineProperty(object, name, {
181-
get: () => lib,
182-
set: setReal,
183-
configurable: true,
184-
enumerable: false
185-
});
186-
187-
return lib;
188-
},
189-
set: setReal,
190-
configurable: true,
191-
enumerable: false
170+
ObjectDefineProperty(object, name, {
171+
get() {
172+
const lib = dummyModule.require(mod);
173+
174+
const submodules = builtinModules
175+
.filter((module) => module.startsWith(`${mod}/`));
176+
if (submodules.length > 0)
177+
attachModulesWrapper(lib, mod, submodules);
178+
179+
// Disable the current getter/setter and set up a new
180+
// non-enumerable property.
181+
delete object[name];
182+
ObjectDefineProperty(object, name, {
183+
value: lib,
184+
writable: true,
185+
configurable: true,
186+
enumerable: false,
187+
});
188+
return lib;
189+
},
190+
191+
set(val) {
192+
// Deleting the property before re-assigning it disables the
193+
// getter/setter mechanism.
194+
delete object[name];
195+
object[name] = val;
196+
},
197+
configurable: true,
198+
enumerable: false,
199+
});
192200
});
193-
});
201+
}
202+
203+
attachModulesWrapper(object, '', builtinModules);
194204
}
195205

196206
function normalizeReferrerURL(referrer) {

test/parallel/test-repl-autolibs.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ function test1() {
3636
putIn.write = function(data) {
3737
gotWrite = true;
3838
if (data.length) {
39-
4039
// Inspect output matches repl output
4140
assert.strictEqual(data,
4241
`${util.inspect(require('fs'), null, 2, false)}\n`);
@@ -59,6 +58,7 @@ function test2() {
5958
assert.strictEqual(data, '{}\n');
6059
// Original value wasn't overwritten
6160
assert.strictEqual(val, global.url);
61+
test3();
6262
}
6363
};
6464
const val = {};
@@ -68,3 +68,20 @@ function test2() {
6868
putIn.run(['url']);
6969
assert(gotWrite);
7070
}
71+
72+
function test3() {
73+
let gotWrite = false;
74+
putIn.write = function(data) {
75+
gotWrite = true;
76+
if (data.length) {
77+
// Inspect output matches repl output
78+
assert.strictEqual(data,
79+
`${util.inspect(require('stream/consumers'), null, 2, false)}\n`);
80+
// Globally added lib matches required lib
81+
assert.strictEqual(global.stream.consumers, require('stream/consumers'));
82+
}
83+
};
84+
assert(!gotWrite);
85+
putIn.run(['stream.consumers']);
86+
assert(gotWrite);
87+
}

0 commit comments

Comments
 (0)