Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 4052aec

Browse filesBrowse files
joyeecheungBridgeAR
authored andcommitted
console: lazy load process.stderr and process.stdout
This patch: - Refactors the Console constructor: moves the property binding code into and the writable streams binding code into two methods defined on the Console.prototype with symbols. - Refactors the global console creation: we only need to share the property binding code from the Console constructor. To bind the streams we can lazy load `process.stdio` and `process.stderr` so that we don't create these streams when they are not used. This significantly reduces the number of modules loaded during bootstrap. Also, by calling the refactored-out method directly we can skip the unnecessary typechecks when creating the global console and there is no need to create a temporary Console anymore. - Refactors the error handler creation and the `write` method: use a `kUseStdout` symbol to tell the internals which stream should be loaded from the console instance. Also put the `write` method on the Console prototype so it just loads other properties directly off the console instance which simplifies the call sites. Also leaves a few TODOs for further refactoring of the console bootstrap. PR-URL: #24534 Reviewed-By: Gus Caplan <me@gus.host>
1 parent 7f5bb9d commit 4052aec
Copy full SHA for 4052aec

File tree

Expand file treeCollapse file tree

3 files changed

+133
-84
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

3 files changed

+133
-84
lines changed
Open diff view settings
Collapse file

‎lib/console.js‎

Copy file name to clipboardExpand all lines: lib/console.js
+122-75Lines changed: 122 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,15 @@ const kFormatForStdout = Symbol('kFormatForStdout');
6565
const kGetInspectOptions = Symbol('kGetInspectOptions');
6666
const kColorMode = Symbol('kColorMode');
6767
const kIsConsole = Symbol('kIsConsole');
68-
68+
const kWriteToConsole = Symbol('kWriteToConsole');
69+
const kBindProperties = Symbol('kBindProperties');
70+
const kBindStreamsEager = Symbol('kBindStreamsEager');
71+
const kBindStreamsLazy = Symbol('kBindStreamsLazy');
72+
const kUseStdout = Symbol('kUseStdout');
73+
const kUseStderr = Symbol('kUseStderr');
74+
75+
// This constructor is not used to construct the global console.
76+
// It's exported for backwards compatibility.
6977
function Console(options /* or: stdout, stderr, ignoreErrors = true */) {
7078
// We have to test new.target here to see if this function is called
7179
// with new, because we need to define a custom instanceof to accommodate
@@ -74,7 +82,6 @@ function Console(options /* or: stdout, stderr, ignoreErrors = true */) {
7482
return new Console(...arguments);
7583
}
7684

77-
this[kIsConsole] = true;
7885
if (!options || typeof options.write === 'function') {
7986
options = {
8087
stdout: options,
@@ -97,37 +104,9 @@ function Console(options /* or: stdout, stderr, ignoreErrors = true */) {
97104
throw new ERR_CONSOLE_WRITABLE_STREAM('stderr');
98105
}
99106

100-
const prop = {
101-
writable: true,
102-
enumerable: false,
103-
configurable: true
104-
};
105-
Object.defineProperty(this, '_stdout', { ...prop, value: stdout });
106-
Object.defineProperty(this, '_stderr', { ...prop, value: stderr });
107-
Object.defineProperty(this, '_ignoreErrors', {
108-
...prop,
109-
value: Boolean(ignoreErrors),
110-
});
111-
Object.defineProperty(this, '_times', { ...prop, value: new Map() });
112-
Object.defineProperty(this, '_stdoutErrorHandler', {
113-
...prop,
114-
value: createWriteErrorHandler(stdout),
115-
});
116-
Object.defineProperty(this, '_stderrErrorHandler', {
117-
...prop,
118-
value: createWriteErrorHandler(stderr),
119-
});
120-
121107
if (typeof colorMode !== 'boolean' && colorMode !== 'auto')
122108
throw new ERR_INVALID_ARG_VALUE('colorMode', colorMode);
123109

124-
// Corresponds to https://console.spec.whatwg.org/#count-map
125-
this[kCounts] = new Map();
126-
this[kColorMode] = colorMode;
127-
128-
Object.defineProperty(this, kGroupIndent, { writable: true });
129-
this[kGroupIndent] = '';
130-
131110
// Bind the prototype functions to this Console instance
132111
var keys = Object.keys(Console.prototype);
133112
for (var v = 0; v < keys.length; v++) {
@@ -137,14 +116,92 @@ function Console(options /* or: stdout, stderr, ignoreErrors = true */) {
137116
// from the prototype chain of the subclass.
138117
this[k] = this[k].bind(this);
139118
}
119+
120+
this[kBindStreamsEager](stdout, stderr);
121+
this[kBindProperties](ignoreErrors, colorMode);
140122
}
141123

124+
const consolePropAttributes = {
125+
writable: true,
126+
enumerable: false,
127+
configurable: true
128+
};
129+
130+
// Fixup global.console instanceof global.console.Console
131+
Object.defineProperty(Console, Symbol.hasInstance, {
132+
value(instance) {
133+
return instance[kIsConsole];
134+
}
135+
});
136+
137+
// Eager version for the Console constructor
138+
Console.prototype[kBindStreamsEager] = function(stdout, stderr) {
139+
Object.defineProperties(this, {
140+
'_stdout': { ...consolePropAttributes, value: stdout },
141+
'_stderr': { ...consolePropAttributes, value: stderr }
142+
});
143+
};
144+
145+
// Lazily load the stdout and stderr from an object so we don't
146+
// create the stdio streams when they are not even accessed
147+
Console.prototype[kBindStreamsLazy] = function(object) {
148+
let stdout;
149+
let stderr;
150+
Object.defineProperties(this, {
151+
'_stdout': {
152+
enumerable: false,
153+
configurable: true,
154+
get() {
155+
if (!stdout) stdout = object.stdout;
156+
return stdout;
157+
},
158+
set(value) { stdout = value; }
159+
},
160+
'_stderr': {
161+
enumerable: false,
162+
configurable: true,
163+
get() {
164+
if (!stderr) { stderr = object.stderr; }
165+
return stderr;
166+
},
167+
set(value) { stderr = value; }
168+
}
169+
});
170+
};
171+
172+
Console.prototype[kBindProperties] = function(ignoreErrors, colorMode) {
173+
Object.defineProperties(this, {
174+
'_stdoutErrorHandler': {
175+
...consolePropAttributes,
176+
value: createWriteErrorHandler(this, kUseStdout)
177+
},
178+
'_stderrErrorHandler': {
179+
...consolePropAttributes,
180+
value: createWriteErrorHandler(this, kUseStderr)
181+
},
182+
'_ignoreErrors': {
183+
...consolePropAttributes,
184+
value: Boolean(ignoreErrors)
185+
},
186+
'_times': { ...consolePropAttributes, value: new Map() }
187+
});
188+
189+
// TODO(joyeecheung): use consolePropAttributes for these
190+
// Corresponds to https://console.spec.whatwg.org/#count-map
191+
this[kCounts] = new Map();
192+
this[kColorMode] = colorMode;
193+
this[kIsConsole] = true;
194+
this[kGroupIndent] = '';
195+
};
196+
142197
// Make a function that can serve as the callback passed to `stream.write()`.
143-
function createWriteErrorHandler(stream) {
198+
function createWriteErrorHandler(instance, streamSymbol) {
144199
return (err) => {
145200
// This conditional evaluates to true if and only if there was an error
146201
// that was not already emitted (which happens when the _write callback
147202
// is invoked asynchronously).
203+
const stream = streamSymbol === kUseStdout ?
204+
instance._stdout : instance._stderr;
148205
if (err !== null && !stream._writableState.errorEmitted) {
149206
// If there was an error, it will be emitted on `stream` as
150207
// an `error` event. Adding a `once` listener will keep that error
@@ -158,7 +215,15 @@ function createWriteErrorHandler(stream) {
158215
};
159216
}
160217

161-
function write(ignoreErrors, stream, string, errorhandler, groupIndent) {
218+
Console.prototype[kWriteToConsole] = function(streamSymbol, string) {
219+
const ignoreErrors = this._ignoreErrors;
220+
const groupIndent = this[kGroupIndent];
221+
222+
const useStdout = streamSymbol === kUseStdout;
223+
const stream = useStdout ? this._stdout : this._stderr;
224+
const errorHandler = useStdout ?
225+
this._stdoutErrorHandler : this._stderrErrorHandler;
226+
162227
if (groupIndent.length !== 0) {
163228
if (string.indexOf('\n') !== -1) {
164229
string = string.replace(/\n/g, `\n${groupIndent}`);
@@ -176,7 +241,7 @@ function write(ignoreErrors, stream, string, errorhandler, groupIndent) {
176241
// Add and later remove a noop error handler to catch synchronous errors.
177242
stream.once('error', noop);
178243

179-
stream.write(string, errorhandler);
244+
stream.write(string, errorHandler);
180245
} catch (e) {
181246
// Console is a debugging utility, so it swallowing errors is not desirable
182247
// even in edge cases such as low stack space.
@@ -186,7 +251,7 @@ function write(ignoreErrors, stream, string, errorhandler, groupIndent) {
186251
} finally {
187252
stream.removeListener('error', noop);
188253
}
189-
}
254+
};
190255

191256
const kColorInspectOptions = { colors: true };
192257
const kNoColorInspectOptions = {};
@@ -212,23 +277,17 @@ Console.prototype[kFormatForStderr] = function(args) {
212277
};
213278

214279
Console.prototype.log = function log(...args) {
215-
write(this._ignoreErrors,
216-
this._stdout,
217-
this[kFormatForStdout](args),
218-
this._stdoutErrorHandler,
219-
this[kGroupIndent]);
280+
this[kWriteToConsole](kUseStdout, this[kFormatForStdout](args));
220281
};
282+
221283
Console.prototype.debug = Console.prototype.log;
222284
Console.prototype.info = Console.prototype.log;
223285
Console.prototype.dirxml = Console.prototype.log;
224286

225287
Console.prototype.warn = function warn(...args) {
226-
write(this._ignoreErrors,
227-
this._stderr,
228-
this[kFormatForStderr](args),
229-
this._stderrErrorHandler,
230-
this[kGroupIndent]);
288+
this[kWriteToConsole](kUseStderr, this[kFormatForStderr](args));
231289
};
290+
232291
Console.prototype.error = Console.prototype.warn;
233292

234293
Console.prototype.dir = function dir(object, options) {
@@ -237,11 +296,7 @@ Console.prototype.dir = function dir(object, options) {
237296
...this[kGetInspectOptions](this._stdout),
238297
...options
239298
};
240-
write(this._ignoreErrors,
241-
this._stdout,
242-
util.inspect(object, options),
243-
this._stdoutErrorHandler,
244-
this[kGroupIndent]);
299+
this[kWriteToConsole](kUseStdout, util.inspect(object, options));
245300
};
246301

247302
Console.prototype.time = function time(label = 'default') {
@@ -301,7 +356,7 @@ Console.prototype.trace = function trace(...args) {
301356
Console.prototype.assert = function assert(expression, ...args) {
302357
if (!expression) {
303358
args[0] = `Assertion failed${args.length === 0 ? '' : `: ${args[0]}`}`;
304-
this.warn(this[kFormatForStderr](args));
359+
this.warn(...args); // the arguments will be formatted in warn() again
305360
}
306361
};
307362

@@ -363,7 +418,6 @@ const valuesKey = 'Values';
363418
const indexKey = '(index)';
364419
const iterKey = '(iteration index)';
365420

366-
367421
const isArray = (v) => ArrayIsArray(v) || isTypedArray(v) || isBuffer(v);
368422

369423
// https://console.spec.whatwg.org/#table
@@ -490,38 +544,31 @@ function noop() {}
490544
// we cannot actually use `new Console` to construct the global console.
491545
// Therefore, the console.Console.prototype is not
492546
// in the global console prototype chain anymore.
547+
548+
// TODO(joyeecheung):
549+
// - Move the Console constructor into internal/console.js
550+
// - Move the global console creation code along with the inspector console
551+
// wrapping code in internal/bootstrap/node.js into a separate file.
552+
// - Make this file a simple re-export of those two files.
493553
// This is only here for v11.x conflict resolution.
494554
const globalConsole = Object.create(Console.prototype);
495-
const tempConsole = new Console({
496-
stdout: process.stdout,
497-
stderr: process.stderr
498-
});
499555

500556
// Since Console is not on the prototype chain of the global console,
501557
// the symbol properties on Console.prototype have to be looked up from
502-
// the global console itself.
503-
for (const prop of Object.getOwnPropertySymbols(Console.prototype)) {
504-
globalConsole[prop] = Console.prototype[prop];
505-
}
506-
507-
// Reflect.ownKeys() is used here for retrieving Symbols
508-
for (const prop of Reflect.ownKeys(tempConsole)) {
509-
const desc = { ...(Reflect.getOwnPropertyDescriptor(tempConsole, prop)) };
510-
// Since Console would bind method calls onto the instance,
511-
// make sure the methods are called on globalConsole instead of
512-
// tempConsole.
513-
if (typeof Console.prototype[prop] === 'function') {
514-
desc.value = Console.prototype[prop].bind(globalConsole);
558+
// the global console itself. In addition, we need to make the global
559+
// console a namespace by binding the console methods directly onto
560+
// the global console with the receiver fixed.
561+
for (const prop of Reflect.ownKeys(Console.prototype)) {
562+
if (prop === 'constructor') { continue; }
563+
const desc = Reflect.getOwnPropertyDescriptor(Console.prototype, prop);
564+
if (typeof desc.value === 'function') { // fix the receiver
565+
desc.value = desc.value.bind(globalConsole);
515566
}
516567
Reflect.defineProperty(globalConsole, prop, desc);
517568
}
518569

519-
globalConsole.Console = Console;
520-
521-
Object.defineProperty(Console, Symbol.hasInstance, {
522-
value(instance) {
523-
return instance[kIsConsole];
524-
}
525-
});
570+
globalConsole[kBindStreamsLazy](process);
571+
globalConsole[kBindProperties](true, 'auto');
526572

527573
module.exports = globalConsole;
574+
module.exports.Console = Console;
Collapse file
+10-8Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
/* eslint-disable node-core/required-modules */
2-
1+
// Flags: --expose-internals
32
'use strict';
43

5-
// Ordinarily test files must require('common') but that action causes
6-
// the global console to be compiled, defeating the purpose of this test.
7-
// This makes sure no additional files are added without carefully considering
8-
// lazy loading. Please adjust the value if necessary.
9-
4+
// This list must be computed before we require any modules to
5+
// to eliminate the noise.
106
const list = process.moduleLoadList.slice();
117

8+
const common = require('../common');
129
const assert = require('assert');
1310

14-
assert(list.length <= 78, list);
11+
const isMainThread = common.isMainThread;
12+
const kMaxModuleCount = isMainThread ? 56 : 78;
13+
14+
assert(list.length <= kMaxModuleCount,
15+
`Total length: ${list.length}\n` + list.join('\n')
16+
);
Collapse file
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
calling stdout._refreshSize
21
calling stderr._refreshSize
2+
calling stdout._refreshSize

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.