diff --git a/lib/.eslintrc.yaml b/lib/.eslintrc.yaml index 80cada0b8c9ecc..e7cab0ad931b4d 100644 --- a/lib/.eslintrc.yaml +++ b/lib/.eslintrc.yaml @@ -20,7 +20,7 @@ rules: - selector: "NewExpression[callee.name=/Error$/]:not([callee.name=/^(AssertionError|NghttpError)$/])" message: "Use an error exported by the internal/errors module." # Custom rules in tools/eslint-rules - node-core/require-buffer: error + node-core/require-globals: error node-core/buffer-constructor: error node-core/no-let-in-for-declaration: error node-core/lowercase-name-for-primitive: error diff --git a/lib/_stream_readable.js b/lib/_stream_readable.js index 8073e174cc586f..74cba547f6b773 100644 --- a/lib/_stream_readable.js +++ b/lib/_stream_readable.js @@ -38,9 +38,11 @@ const { ERR_METHOD_NOT_IMPLEMENTED, ERR_STREAM_UNSHIFT_AFTER_END_EVENT } = require('internal/errors').codes; -const ReadableAsyncIterator = require('internal/streams/async_iterator'); const { emitExperimentalWarning } = require('internal/util'); -var StringDecoder; + +// Lazy loaded to improve the startup performance. +let StringDecoder; +let ReadableAsyncIterator; util.inherits(Readable, Stream); @@ -985,7 +987,8 @@ Readable.prototype.wrap = function(stream) { Readable.prototype[Symbol.asyncIterator] = function() { emitExperimentalWarning('Readable[Symbol.asyncIterator]'); - + if (ReadableAsyncIterator === undefined) + ReadableAsyncIterator = require('internal/streams/async_iterator'); return new ReadableAsyncIterator(this); }; diff --git a/lib/assert.js b/lib/assert.js index 45ddbe5f817d36..4dafdd2bf04a54 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -21,10 +21,6 @@ 'use strict'; const { Buffer } = require('buffer'); -const { - isDeepEqual, - isDeepStrictEqual -} = require('internal/util/comparisons'); const { codes: { ERR_AMBIGUOUS_ARGUMENT, ERR_INVALID_ARG_TYPE, @@ -34,9 +30,18 @@ const { codes: { const { AssertionError, errorCache } = require('internal/assert'); const { openSync, closeSync, readSync } = require('fs'); const { inspect, types: { isPromise, isRegExp } } = require('util'); -const { EOL } = require('os'); +const { EOL } = require('internal/constants'); const { NativeModule } = require('internal/bootstrap/loaders'); +let isDeepEqual; +let isDeepStrictEqual; + +function lazyLoadComparison() { + const comparison = require('internal/util/comparisons'); + isDeepEqual = comparison.isDeepEqual; + isDeepStrictEqual = comparison.isDeepStrictEqual; +} + // Escape control characters but not \n and \t to keep the line breaks and // indentation intact. // eslint-disable-next-line no-control-regex @@ -285,6 +290,7 @@ assert.notEqual = function notEqual(actual, expected, message) { // The equivalence assertion tests a deep equality relation. assert.deepEqual = function deepEqual(actual, expected, message) { + if (isDeepEqual === undefined) lazyLoadComparison(); if (!isDeepEqual(actual, expected)) { innerFail({ actual, @@ -298,6 +304,7 @@ assert.deepEqual = function deepEqual(actual, expected, message) { // The non-equivalence assertion tests for any deep inequality. assert.notDeepEqual = function notDeepEqual(actual, expected, message) { + if (isDeepEqual === undefined) lazyLoadComparison(); if (isDeepEqual(actual, expected)) { innerFail({ actual, @@ -311,6 +318,7 @@ assert.notDeepEqual = function notDeepEqual(actual, expected, message) { /* eslint-enable */ assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) { + if (isDeepEqual === undefined) lazyLoadComparison(); if (!isDeepStrictEqual(actual, expected)) { innerFail({ actual, @@ -324,6 +332,7 @@ assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) { assert.notDeepStrictEqual = notDeepStrictEqual; function notDeepStrictEqual(actual, expected, message) { + if (isDeepEqual === undefined) lazyLoadComparison(); if (isDeepStrictEqual(actual, expected)) { innerFail({ actual, @@ -437,6 +446,7 @@ function expectedException(actual, expected, msg) { throw new ERR_INVALID_ARG_VALUE('error', expected, 'may not be an empty object'); } + if (isDeepEqual === undefined) lazyLoadComparison(); for (const key of keys) { if (typeof actual[key] === 'string' && isRegExp(expected[key]) && diff --git a/lib/console.js b/lib/console.js index 46b7f7627eddae..868b2f286bedf4 100644 --- a/lib/console.js +++ b/lib/console.js @@ -31,7 +31,6 @@ const { } = require('internal/errors'); const { previewMapIterator, previewSetIterator } = require('internal/v8'); const { Buffer: { isBuffer } } = require('buffer'); -const cliTable = require('internal/cli_table'); const util = require('util'); const { isTypedArray, isSet, isMap, isSetIterator, isMapIterator, @@ -49,6 +48,9 @@ const { from: ArrayFrom, } = Array; +// Lazy loaded for startup performance. +let cliTable; + // Track amount of indentation required via `console.group()`. const kGroupIndent = Symbol('kGroupIndent'); @@ -329,6 +331,7 @@ Console.prototype.table = function(tabularData, properties) { (typeof tabularData !== 'object' && typeof tabularData !== 'function')) return this.log(tabularData); + if (cliTable === undefined) cliTable = require('internal/cli_table'); const final = (k, v) => this.log(cliTable(k, v)); const inspect = (v) => { diff --git a/lib/dgram.js b/lib/dgram.js index 0a72834873e8bd..f3454ffc0702c4 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -35,7 +35,6 @@ const { ERR_SOCKET_DGRAM_NOT_RUNNING } = errors.codes; const { Buffer } = require('buffer'); -const dns = require('dns'); const util = require('util'); const { isUint8Array } = require('internal/util/types'); const EventEmitter = require('events'); @@ -47,6 +46,9 @@ const { UV_UDP_REUSEADDR } = process.binding('constants').os; const { UDP, SendWrap } = process.binding('udp_wrap'); +// Lazy load for startup performance. +let dns; + const BIND_STATE_UNBOUND = 0; const BIND_STATE_BINDING = 1; const BIND_STATE_BOUND = 2; @@ -72,9 +74,10 @@ function lookup6(lookup, address, callback) { function newHandle(type, lookup) { - if (lookup === undefined) + if (lookup === undefined) { + if (dns === undefined) dns = require('dns'); lookup = dns.lookup; - else if (typeof lookup !== 'function') + } else if (typeof lookup !== 'function') throw new ERR_INVALID_ARG_TYPE('lookup', 'Function', lookup); if (type === 'udp4') { diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 5993cc71f7d404..e23ec858f33cc8 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -26,7 +26,7 @@ setupProcessObject(); - // do this good and early, since it handles errors. + // Do this good and early, since it handles errors. setupProcessFatal(); setupV8(); @@ -57,15 +57,27 @@ } = perf.constants; _process.setup_hrtime(); - _process.setup_performance(); _process.setup_cpuUsage(); _process.setupMemoryUsage(); _process.setupKillAndExit(); if (global.__coverage__) NativeModule.require('internal/process/write-coverage').setup(); - NativeModule.require('internal/trace_events_async_hooks').setup(); - NativeModule.require('internal/inspector_async_hook').setup(); + + { + const traceEvents = process.binding('trace_events'); + const traceEventCategory = 'node,node.async_hooks'; + + if (traceEvents.categoryGroupEnabled(traceEventCategory)) { + NativeModule.require('internal/trace_events_async_hooks') + .setup(traceEvents, traceEventCategory); + } + } + + + if (process.config.variables.v8_enable_inspector) { + NativeModule.require('internal/inspector_async_hook').setup(); + } _process.setupChannel(); _process.setupRawDebug(); @@ -77,10 +89,6 @@ setupGlobalURL(); } - // Ensure setURLConstructor() is called before the native - // URL::ToObject() method is used. - NativeModule.require('internal/url'); - // On OpenBSD process.execPath will be relative unless we // get the full path before process.execPath is used. if (process.platform === 'openbsd') { @@ -95,7 +103,7 @@ }); process.argv[0] = process.execPath; - // Handle `--debug*` deprecation and invalidation + // Handle `--debug*` deprecation and invalidation. if (process._invalidDebug) { process.emitWarning( '`node --debug` and `node --debug-brk` are invalid. ' + @@ -164,7 +172,7 @@ 'DeprecationWarning', 'DEP0068'); } - // Start the debugger agent + // Start the debugger agent. process.nextTick(function() { NativeModule.require('internal/deps/node-inspect/lib/_inspect').start(); }); @@ -173,7 +181,7 @@ NativeModule.require('internal/v8_prof_processor'); } else { - // There is user code to be run + // There is user code to be run. // If this is a worker in cluster mode, start up the communication // channel. This needs to be done before any user code gets executed @@ -191,7 +199,7 @@ perf.markMilestone(NODE_PERFORMANCE_MILESTONE_MODULE_LOAD_START); perf.markMilestone(NODE_PERFORMANCE_MILESTONE_MODULE_LOAD_END); // User passed '-e' or '--eval' arguments to Node without '-i' or - // '--interactive' + // '--interactive'. perf.markMilestone( NODE_PERFORMANCE_MILESTONE_PRELOAD_MODULE_LOAD_START); @@ -205,7 +213,7 @@ evalScript('[eval]'); } else if (process.argv[1] && process.argv[1] !== '-') { perf.markMilestone(NODE_PERFORMANCE_MILESTONE_MODULE_LOAD_START); - // make process.argv[1] into a full path + // Make process.argv[1] into a full path. const path = NativeModule.require('path'); process.argv[1] = path.resolve(process.argv[1]); @@ -217,10 +225,10 @@ preloadModules(); perf.markMilestone( NODE_PERFORMANCE_MILESTONE_PRELOAD_MODULE_LOAD_END); - // check if user passed `-c` or `--check` arguments to Node. + // Check if user passed `-c` or `--check` arguments to Node. if (process._syntax_check_only != null) { const fs = NativeModule.require('fs'); - // read the source + // Read the source. const filename = CJSModule._resolveFilename(process.argv[1]); const source = fs.readFileSync(filename, 'utf-8'); checkScriptSyntax(source, filename); @@ -352,7 +360,7 @@ function setupGlobalConsole() { const originalConsole = global.console; const CJSModule = NativeModule.require('internal/modules/cjs/loader'); - // Setup Node.js global.console + // Setup Node.js global.console. const wrappedConsole = NativeModule.require('console'); Object.defineProperty(global, 'console', { configurable: true, @@ -386,7 +394,7 @@ return; } const { addCommandLineAPI, consoleCall } = process.binding('inspector'); - // Setup inspector command line API + // Setup inspector command line API. const { makeRequireFunction } = NativeModule.require('internal/modules/cjs/helpers'); const path = NativeModule.require('path'); @@ -436,14 +444,14 @@ exceptionHandlerState.captureFn(er); } else if (!process.emit('uncaughtException', er)) { // If someone handled it, then great. otherwise, die in C++ land - // since that means that we'll exit the process, emit the 'exit' event + // since that means that we'll exit the process, emit the 'exit' event. try { if (!process._exiting) { process._exiting = true; process.emit('exit', 1); } - } catch (er) { - // nothing to be done about it at this point. + } catch { + // Nothing to be done about it at this point. } try { const { kExpandStackSymbol } = NativeModule.require('internal/util'); @@ -454,7 +462,7 @@ } // If we handled an error, then make sure any ticks get processed - // by ensuring that the next Immediate cycle isn't empty + // by ensuring that the next Immediate cycle isn't empty. NativeModule.require('timers').setImmediate(noop); // Emit the after() hooks now that the exception has been handled. @@ -547,7 +555,7 @@ process._tickCallback(); } - // Load preload modules + // Load preload modules. function preloadModules() { if (process._preload_modules) { const { @@ -564,13 +572,13 @@ stripShebang, stripBOM } = NativeModule.require('internal/modules/cjs/helpers'); - // remove Shebang + // Remove Shebang. source = stripShebang(source); - // remove BOM + // Remove BOM. source = stripBOM(source); - // wrap it + // Wrap it. source = CJSModule.wrap(source); - // compile the script, this will throw if it fails + // Compile the script, this will throw if it fails. new vm.Script(source, { displayErrors: true, filename }); } diff --git a/lib/internal/child_process.js b/lib/internal/child_process.js index a630cff717bcad..1ec3f208660910 100644 --- a/lib/internal/child_process.js +++ b/lib/internal/child_process.js @@ -14,7 +14,6 @@ const { ERR_MISSING_ARGS } } = require('internal/errors'); -const { StringDecoder } = require('string_decoder'); const EventEmitter = require('events'); const net = require('net'); const dgram = require('dgram'); @@ -47,6 +46,9 @@ const { const { SocketListSend, SocketListReceive } = SocketList; +// Lazy loaded for startup performance. +let StringDecoder; + const MAX_HANDLE_RETRANSMISSIONS = 3; // this object contain function to convert TCP objects to native handle objects @@ -476,6 +478,8 @@ function setupChannel(target, channel) { const control = new Control(channel); + if (StringDecoder === undefined) + StringDecoder = require('string_decoder').StringDecoder; var decoder = new StringDecoder('utf8'); var jsonBuffer = ''; var pendingHandle = null; diff --git a/lib/internal/cluster/master.js b/lib/internal/cluster/master.js index 8e4f9395da6643..457b3579b12c1c 100644 --- a/lib/internal/cluster/master.js +++ b/lib/internal/cluster/master.js @@ -275,12 +275,8 @@ function queryServer(worker, message) { if (worker.exitedAfterDisconnect) return; - const args = [message.address, - message.port, - message.addressType, - message.fd, - message.index]; - const key = args.join(':'); + const key = `${message.address}:${message.port}:${message.addressType}:` + + `${message.fd}:${message.index}`; var handle = handles[key]; if (handle === undefined) { diff --git a/lib/internal/constants.js b/lib/internal/constants.js index 5c884ae6bc4a7f..dfa30bea306e65 100644 --- a/lib/internal/constants.js +++ b/lib/internal/constants.js @@ -1,5 +1,7 @@ 'use strict'; +const isWindows = process.platform === 'win32'; + module.exports = { // Alphabet chars. CHAR_UPPERCASE_A: 65, /* A */ @@ -45,4 +47,6 @@ module.exports = { // Digits CHAR_0: 48, /* 0 */ CHAR_9: 57, /* 9 */ + + EOL: isWindows ? '\r\n' : '\n' }; diff --git a/lib/internal/crypto/cipher.js b/lib/internal/crypto/cipher.js index d1c45bfdce0e0d..bd80c19697e70b 100644 --- a/lib/internal/crypto/cipher.js +++ b/lib/internal/crypto/cipher.js @@ -28,11 +28,13 @@ const { const assert = require('assert'); const LazyTransform = require('internal/streams/lazy_transform'); -const { StringDecoder } = require('string_decoder'); const { inherits } = require('util'); const { normalizeEncoding } = require('internal/util'); +// Lazy loaded for startup performance. +let StringDecoder; + function rsaFunctionFor(method, defaultPadding) { return function(options, buffer) { const key = options.key || options; @@ -49,6 +51,8 @@ const privateDecrypt = rsaFunctionFor(_privateDecrypt, RSA_PKCS1_OAEP_PADDING); function getDecoder(decoder, encoding) { encoding = normalizeEncoding(encoding); + if (StringDecoder === undefined) + StringDecoder = require('string_decoder').StringDecoder; decoder = decoder || new StringDecoder(encoding); assert(decoder.encoding === encoding, 'Cannot change encoding'); return decoder; diff --git a/lib/internal/inspector_async_hook.js b/lib/internal/inspector_async_hook.js index b190ff90b8ea2c..6a7489af974a40 100644 --- a/lib/internal/inspector_async_hook.js +++ b/lib/internal/inspector_async_hook.js @@ -1,49 +1,56 @@ 'use strict'; -const { createHook } = require('async_hooks'); const inspector = process.binding('inspector'); -const config = process.binding('config'); if (!inspector || !inspector.asyncTaskScheduled) { exports.setup = function() {}; return; } -const hook = createHook({ - init(asyncId, type, triggerAsyncId, resource) { +let hook; +let config; + +function lazyHookCreation() { + const { createHook } = require('async_hooks'); + config = process.binding('config'); + + hook = createHook({ + init(asyncId, type, triggerAsyncId, resource) { // It's difficult to tell which tasks will be recurring and which won't, // therefore we mark all tasks as recurring. Based on the discussion // in https://github.com/nodejs/node/pull/13870#discussion_r124515293, // this should be fine as long as we call asyncTaskCanceled() too. - const recurring = true; - if (type === 'PROMISE') - this.promiseIds.add(asyncId); - else - inspector.asyncTaskScheduled(type, asyncId, recurring); - }, + const recurring = true; + if (type === 'PROMISE') + this.promiseIds.add(asyncId); + else + inspector.asyncTaskScheduled(type, asyncId, recurring); + }, - before(asyncId) { - if (this.promiseIds.has(asyncId)) - return; - inspector.asyncTaskStarted(asyncId); - }, + before(asyncId) { + if (this.promiseIds.has(asyncId)) + return; + inspector.asyncTaskStarted(asyncId); + }, - after(asyncId) { - if (this.promiseIds.has(asyncId)) - return; - inspector.asyncTaskFinished(asyncId); - }, + after(asyncId) { + if (this.promiseIds.has(asyncId)) + return; + inspector.asyncTaskFinished(asyncId); + }, - destroy(asyncId) { - if (this.promiseIds.has(asyncId)) - return this.promiseIds.delete(asyncId); - inspector.asyncTaskCanceled(asyncId); - }, -}); + destroy(asyncId) { + if (this.promiseIds.has(asyncId)) + return this.promiseIds.delete(asyncId); + inspector.asyncTaskCanceled(asyncId); + }, + }); -hook.promiseIds = new Set(); + hook.promiseIds = new Set(); +} function enable() { + if (hook === undefined) lazyHookCreation(); if (config.bits < 64) { // V8 Inspector stores task ids as (void*) pointers. // async_hooks store ids as 64bit numbers. @@ -61,6 +68,7 @@ function enable() { } function disable() { + if (hook === undefined) lazyHookCreation(); hook.disable(); } diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 022f58d27e2868..e471cc013a6531 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -23,8 +23,6 @@ const { NativeModule } = require('internal/bootstrap/loaders'); const util = require('util'); -const { decorateErrorStack } = require('internal/util'); -const { getURLFromFilePath } = require('internal/url'); const vm = require('vm'); const assert = require('assert').ok; const fs = require('fs'); @@ -53,11 +51,21 @@ const { module.exports = Module; -// these are below module.exports for the circular reference -const asyncESM = require('internal/process/esm_loader'); -const ModuleJob = require('internal/modules/esm/module_job'); -const createDynamicModule = require( - 'internal/modules/esm/create_dynamic_module'); +let asyncESM; +let ModuleJob; +let createDynamicModule; +let getURLFromFilePath; +let decorateErrorStack; + +function lazyLoadESM() { + asyncESM = require('internal/process/esm_loader'); + ModuleJob = require('internal/modules/esm/module_job'); + createDynamicModule = require( + 'internal/modules/esm/create_dynamic_module'); + decorateErrorStack = require('internal/util').decorateErrorStack; + getURLFromFilePath = require('internal/url').getURLFromFilePath; +} + const { CHAR_UPPERCASE_A, CHAR_LOWERCASE_A, @@ -497,6 +505,7 @@ Module._load = function(request, parent, isMain) { } if (experimentalModules && isMain) { + if (asyncESM === undefined) lazyLoadESM(); asyncESM.loaderPromise.then((loader) => { return loader.import(getURLFromFilePath(request).pathname); }) @@ -604,6 +613,7 @@ Module.prototype.load = function(filename) { this.loaded = true; if (experimentalModules) { + if (asyncESM === undefined) lazyLoadESM(); const ESMLoader = asyncESM.ESMLoader; const url = getURLFromFilePath(filename); const urlString = `${url}`; @@ -722,6 +732,7 @@ Module._extensions['.node'] = function(module, filename) { }; if (experimentalModules) { + if (asyncESM === undefined) lazyLoadESM(); Module._extensions['.mjs'] = function(module, filename) { throw new ERR_REQUIRE_ESM(filename); }; @@ -797,5 +808,5 @@ Module._preloadModules = function(requests) { Module._initPaths(); -// backwards compatibility +// Backwards compatibility Module.Module = Module; diff --git a/lib/internal/process.js b/lib/internal/process.js index cc8318cbe3ce53..3d02e6eb87cc6a 100644 --- a/lib/internal/process.js +++ b/lib/internal/process.js @@ -24,10 +24,6 @@ process.assert = deprecate( 'process.assert() is deprecated. Please use the `assert` module instead.', 'DEP0100'); -function setup_performance() { - require('perf_hooks'); -} - // Set up the process.cpuUsage() function. function setup_cpuUsage() { // Get the native function, which will be replaced with a JS version. @@ -284,7 +280,6 @@ function setupUncaughtExceptionCapture(exceptionHandlerState) { } module.exports = { - setup_performance, setup_cpuUsage, setup_hrtime, setupMemoryUsage, diff --git a/lib/internal/streams/pipeline.js b/lib/internal/streams/pipeline.js index 7e87210a774c5f..1178108842be61 100644 --- a/lib/internal/streams/pipeline.js +++ b/lib/internal/streams/pipeline.js @@ -3,7 +3,7 @@ 'use strict'; -const eos = require('internal/streams/end-of-stream'); +let eos; const { ERR_MISSING_ARGS, @@ -33,6 +33,7 @@ function destroyer(stream, reading, writing, callback) { closed = true; }); + if (eos === undefined) eos = require('internal/streams/end-of-stream'); eos(stream, { readable: reading, writable: writing }, (err) => { if (err) return callback(err); closed = true; diff --git a/lib/internal/trace_events_async_hooks.js b/lib/internal/trace_events_async_hooks.js index db38fe99adcc9c..011dc6bbead187 100644 --- a/lib/internal/trace_events_async_hooks.js +++ b/lib/internal/trace_events_async_hooks.js @@ -1,71 +1,63 @@ 'use strict'; -const trace_events = process.binding('trace_events'); -const async_wrap = process.binding('async_wrap'); -const async_hooks = require('async_hooks'); - -// Use small letters such that chrome://tracing groups by the name. -// The behavior is not only useful but the same as the events emitted using -// the specific C++ macros. -const BEFORE_EVENT = 'b'.charCodeAt(0); -const END_EVENT = 'e'.charCodeAt(0); - -// In trace_events it is not only the id but also the name that needs to be -// repeated. Since async_hooks doesn't expose the provider type in the -// non-init events, use a map to manually map the asyncId to the type name. -const typeMemory = new Map(); - -const trace_event_category = 'node,node.async_hooks'; - -// It is faster to emit trace_events directly from C++. Thus, this happens in -// async_wrap.cc. However, events emitted from the JavaScript API or the -// Embedder C++ API can't be emitted from async_wrap.cc. Thus they are -// emitted using the JavaScript API. To prevent emitting the same event -// twice the async_wrap.Providers list is used to filter the events. -const nativeProviders = new Set(Object.keys(async_wrap.Providers)); - -const hook = async_hooks.createHook({ - init(asyncId, type, triggerAsyncId, resource) { - if (nativeProviders.has(type)) return; - - typeMemory.set(asyncId, type); - trace_events.emit(BEFORE_EVENT, trace_event_category, - type, asyncId, - 'triggerAsyncId', triggerAsyncId, - 'executionAsyncId', async_hooks.executionAsyncId()); - }, - - before(asyncId) { - const type = typeMemory.get(asyncId); - if (type === undefined) return; - - trace_events.emit(BEFORE_EVENT, trace_event_category, - type + '_CALLBACK', asyncId); - }, - - after(asyncId) { - const type = typeMemory.get(asyncId); - if (type === undefined) return; - - trace_events.emit(END_EVENT, trace_event_category, - type + '_CALLBACK', asyncId); - }, - - destroy(asyncId) { - const type = typeMemory.get(asyncId); - if (type === undefined) return; - - trace_events.emit(END_EVENT, trace_event_category, - type, asyncId); - - // cleanup asyncId to type map - typeMemory.delete(asyncId); - } -}); - - -exports.setup = function() { - if (trace_events.categoryGroupEnabled(trace_event_category)) { - hook.enable(); - } +exports.setup = function(traceEvents, traceEventCategory) { + const async_wrap = process.binding('async_wrap'); + const async_hooks = require('async_hooks'); + + // Use small letters such that chrome://tracing groups by the name. + // The behavior is not only useful but the same as the events emitted using + // the specific C++ macros. + const BEFORE_EVENT = 'b'.charCodeAt(0); + const END_EVENT = 'e'.charCodeAt(0); + + // In traceEvents it is not only the id but also the name that needs to be + // repeated. Since async_hooks doesn't expose the provider type in the + // non-init events, use a map to manually map the asyncId to the type name. + const typeMemory = new Map(); + + // It is faster to emit traceEvents directly from C++. Thus, this happens + // in async_wrap.cc. However, events emitted from the JavaScript API or the + // Embedder C++ API can't be emitted from async_wrap.cc. Thus they are + // emitted using the JavaScript API. To prevent emitting the same event + // twice the async_wrap.Providers list is used to filter the events. + const nativeProviders = new Set(Object.keys(async_wrap.Providers)); + + async_hooks.createHook({ + init(asyncId, type, triggerAsyncId, resource) { + if (nativeProviders.has(type)) return; + + typeMemory.set(asyncId, type); + traceEvents.emit(BEFORE_EVENT, traceEventCategory, + type, asyncId, + 'triggerAsyncId', triggerAsyncId, + 'executionAsyncId', async_hooks.executionAsyncId()); + }, + + before(asyncId) { + const type = typeMemory.get(asyncId); + if (type === undefined) return; + + traceEvents.emit(BEFORE_EVENT, traceEventCategory, + type + '_CALLBACK', asyncId); + }, + + after(asyncId) { + const type = typeMemory.get(asyncId); + if (type === undefined) return; + + traceEvents.emit(END_EVENT, traceEventCategory, + type + '_CALLBACK', asyncId); + }, + + destroy(asyncId) { + const type = typeMemory.get(asyncId); + if (type === undefined) return; + + traceEvents.emit(END_EVENT, traceEventCategory, + type, asyncId); + + // cleanup asyncId to type map + typeMemory.delete(asyncId); + } + }).enable(); }; diff --git a/lib/internal/tty.js b/lib/internal/tty.js index c4edab24fb87ab..1e60909f66a636 100644 --- a/lib/internal/tty.js +++ b/lib/internal/tty.js @@ -22,9 +22,7 @@ 'use strict'; -const { release } = require('os'); - -const OSRelease = release().split('.'); +let OSRelease; const COLORS_2 = 1; const COLORS_16 = 4; @@ -75,6 +73,11 @@ function getColorDepth(env = process.env) { } if (process.platform === 'win32') { + // Lazy load for startup performance. + if (OSRelease === undefined) { + const { release } = require('os'); + OSRelease = release().split('.'); + } // Windows 10 build 10586 is the first Windows release that supports 256 // colors. Windows 10 build 14931 is the first release that supports // 16m/TrueColor. diff --git a/lib/internal/url.js b/lib/internal/url.js index d9daef1524787d..5a51e384fc6b1e 100644 --- a/lib/internal/url.js +++ b/lib/internal/url.js @@ -27,7 +27,9 @@ const { CHAR_LOWERCASE_A, CHAR_LOWERCASE_Z, } = require('internal/constants'); -const querystring = require('querystring'); + +// Lazy loaded for startup performance. +let querystring; const { platform } = process; const isWindows = platform === 'win32'; @@ -770,8 +772,10 @@ function parseParams(qs) { } else if (encodeCheck > 0) { // eslint-disable-next-line no-extra-boolean-cast if (!!isHexTable[code]) { - if (++encodeCheck === 3) + if (++encodeCheck === 3) { + querystring = require('querystring'); encoded = true; + } } else { encodeCheck = 0; } diff --git a/lib/net.js b/lib/net.js index 5537c472fe05e3..7d0a4a0e54a255 100644 --- a/lib/net.js +++ b/lib/net.js @@ -75,13 +75,12 @@ const { ERR_SOCKET_BAD_PORT, ERR_SOCKET_CLOSED } = errors.codes; -const dns = require('dns'); const kLastWriteQueueSize = Symbol('lastWriteQueueSize'); -// `cluster` is only used by `listenInCluster` so for startup performance -// reasons it's lazy loaded. -var cluster = null; +// Lazy loaded to improve startup performance. +let cluster; +let dns; const errnoException = errors.errnoException; const exceptionWithHostPort = errors.exceptionWithHostPort; @@ -1034,6 +1033,8 @@ function lookupAndConnect(self, options) { throw new ERR_INVALID_ARG_TYPE('options.lookup', 'Function', options.lookup); + + if (dns === undefined) dns = require('dns'); var dnsopts = { family: options.family, hints: options.hints || 0 @@ -1368,7 +1369,7 @@ function listenInCluster(server, address, port, addressType, backlog, fd, exclusive) { exclusive = !!exclusive; - if (cluster === null) cluster = require('cluster'); + if (cluster === undefined) cluster = require('cluster'); if (cluster.isMaster || exclusive) { // Will create a new handle @@ -1482,6 +1483,7 @@ Server.prototype.listen = function(...args) { }; function lookupAndListen(self, port, address, backlog, exclusive) { + if (dns === undefined) dns = require('dns'); dns.lookup(address, function doListen(err, ip, addressType) { if (err) { self.emit('error', err); diff --git a/lib/readline.js b/lib/readline.js index 124fc8111b2f09..89dd1b84f2dde0 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -35,7 +35,6 @@ const { const { debug, inherits } = require('util'); const { Buffer } = require('buffer'); const EventEmitter = require('events'); -const { StringDecoder } = require('string_decoder'); const { CSI, emitKeys, @@ -52,6 +51,9 @@ const { kClearScreenDown } = CSI; +// Lazy load StringDecoder for startup performance. +let StringDecoder; + const kHistorySize = 30; const kMincrlfDelay = 100; // \r\n, \n, or \r followed by something other than \n @@ -73,6 +75,9 @@ function Interface(input, output, completer, terminal) { return new Interface(input, output, completer, terminal); } + if (StringDecoder === undefined) + StringDecoder = require('string_decoder').StringDecoder; + this._sawReturnAt = 0; this.isCompletionEnabled = true; this._sawKeyPress = false; @@ -987,6 +992,9 @@ Interface.prototype._ttyWrite = function(s, key) { function emitKeypressEvents(stream, iface) { if (stream[KEYPRESS_DECODER]) return; + + if (StringDecoder === undefined) + StringDecoder = require('string_decoder').StringDecoder; stream[KEYPRESS_DECODER] = new StringDecoder('utf8'); stream[ESCAPE_DECODER] = emitKeys(stream); diff --git a/lib/tty.js b/lib/tty.js index 4e9023b0eb6114..b36adf2534c326 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -26,9 +26,11 @@ const net = require('net'); const { TTY, isTTY } = process.binding('tty_wrap'); const errors = require('internal/errors'); const { ERR_INVALID_FD, ERR_TTY_INIT_FAILED } = errors.codes; -const readline = require('readline'); const { getColorDepth } = require('internal/tty'); +// Lazy loaded for startup performance. +let readline; + function isatty(fd) { return Number.isInteger(fd) && fd >= 0 && isTTY(fd); } @@ -122,15 +124,19 @@ WriteStream.prototype._refreshSize = function() { // Backwards-compat WriteStream.prototype.cursorTo = function(x, y) { + if (readline === undefined) readline = require('readline'); readline.cursorTo(this, x, y); }; WriteStream.prototype.moveCursor = function(dx, dy) { + if (readline === undefined) readline = require('readline'); readline.moveCursor(this, dx, dy); }; WriteStream.prototype.clearLine = function(dir) { + if (readline === undefined) readline = require('readline'); readline.clearLine(this, dir); }; WriteStream.prototype.clearScreenDown = function() { + if (readline === undefined) readline = require('readline'); readline.clearScreenDown(this); }; WriteStream.prototype.getWindowSize = function() { diff --git a/lib/url.js b/lib/url.js index e4326e80b5d948..3a2c3c01ea1f30 100644 --- a/lib/url.js +++ b/lib/url.js @@ -30,6 +30,8 @@ const { ERR_INVALID_ARG_TYPE } = require('internal/errors').codes; +// This ensures setURLConstructor() is called before the native +// URL::ToObject() method is used. const { spliceOne } = require('internal/util'); // WHATWG URL implementation provided by internal/url @@ -94,7 +96,6 @@ const slashedProtocol = { 'file': true, 'file:': true }; -const querystring = require('querystring'); const { CHAR_SPACE, CHAR_TAB, @@ -133,6 +134,9 @@ const { CHAR_AT, } = require('internal/constants'); +// Lazy loaded for startup performance. +let querystring; + function urlParse(url, parseQueryString, slashesDenoteHost) { if (url instanceof Url) return url; @@ -233,6 +237,7 @@ Url.prototype.parse = function parse(url, parseQueryString, slashesDenoteHost) { if (simplePath[2]) { this.search = simplePath[2]; if (parseQueryString) { + if (querystring === undefined) querystring = require('querystring'); this.query = querystring.parse(this.search.slice(1)); } else { this.query = this.search.slice(1); @@ -422,6 +427,7 @@ Url.prototype.parse = function parse(url, parseQueryString, slashesDenoteHost) { this.query = rest.slice(questionIdx + 1, hashIdx); } if (parseQueryString) { + if (querystring === undefined) querystring = require('querystring'); this.query = querystring.parse(this.query); } } else if (parseQueryString) { @@ -584,8 +590,10 @@ Url.prototype.format = function format() { } } - if (this.query !== null && typeof this.query === 'object') + if (this.query !== null && typeof this.query === 'object') { + if (querystring === undefined) querystring = require('querystring'); query = querystring.stringify(this.query); + } var search = this.search || (query && ('?' + query)) || ''; diff --git a/lib/util.js b/lib/util.js index 45d98de194c836..bd7a98694b91a9 100644 --- a/lib/util.js +++ b/lib/util.js @@ -64,10 +64,6 @@ const { isTypedArray } = types; -const { - isDeepStrictEqual -} = require('internal/util/comparisons'); - const { customInspectSymbol, deprecate, @@ -95,6 +91,7 @@ const dateToISOString = Date.prototype.toISOString; const errorToString = Error.prototype.toString; let CIRCULAR_ERROR_MESSAGE; +let internalDeepEqual; /* eslint-disable */ const strEscapeSequencesRegExp = /[\x00-\x1f\x27\x5c]/; @@ -1261,7 +1258,13 @@ module.exports = exports = { isArray: Array.isArray, isBoolean, isBuffer, - isDeepStrictEqual, + isDeepStrictEqual(a, b) { + if (internalDeepEqual === undefined) { + internalDeepEqual = require('internal/util/comparisons') + .isDeepStrictEqual; + } + return internalDeepEqual(a, b); + }, isNull, isNullOrUndefined, isNumber, diff --git a/test/parallel/test-bootstrap-modules.js b/test/parallel/test-bootstrap-modules.js new file mode 100644 index 00000000000000..4a46ac905b8161 --- /dev/null +++ b/test/parallel/test-bootstrap-modules.js @@ -0,0 +1,14 @@ +/* eslint-disable node-core/required-modules */ + +'use strict'; + +// Ordinarily test files must require('common') but that action causes +// the global console to be compiled, defeating the purpose of this test. +// This makes sure no additional files are added without carefully considering +// lazy loading. Please adjust the value if necessary. + +const list = process.moduleLoadList.slice(); + +const assert = require('assert'); + +assert(list.length <= 73, list); diff --git a/test/parallel/test-eslint-require-buffer.js b/test/parallel/test-eslint-require-buffer.js index da17d44c7f600d..d928c435480e5e 100644 --- a/test/parallel/test-eslint-require-buffer.js +++ b/test/parallel/test-eslint-require-buffer.js @@ -5,7 +5,7 @@ const common = require('../common'); common.skipIfEslintMissing(); const RuleTester = require('../../tools/node_modules/eslint').RuleTester; -const rule = require('../../tools/eslint-rules/require-buffer'); +const rule = require('../../tools/eslint-rules/require-globals'); const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 }, env: { node: true } @@ -18,7 +18,7 @@ const useStrict = '\'use strict\';\n\n'; const bufferModule = 'const { Buffer } = require(\'buffer\');\n'; const mockComment = '// Some Comment\n//\n// Another Comment\n\n'; const useBuffer = 'Buffer;'; -ruleTester.run('require-buffer', rule, { +ruleTester.run('require-globals', rule, { valid: [ 'foo', 'const Buffer = require("Buffer"); Buffer;', diff --git a/tools/eslint-rules/require-buffer.js b/tools/eslint-rules/require-buffer.js deleted file mode 100644 index b12b9ce04e7cfc..00000000000000 --- a/tools/eslint-rules/require-buffer.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; -const BUFFER_REQUIRE = 'const { Buffer } = require(\'buffer\');'; - -module.exports = function(context) { - - function flagIt(reference) { - const msg = `Use ${BUFFER_REQUIRE} at the beginning of this file`; - - context.report({ - node: reference.identifier, - message: msg, - fix: (fixer) => { - const sourceCode = context.getSourceCode(); - - const useStrict = /'use strict';\n\n?/g; - const hasUseStrict = !!useStrict.exec(sourceCode.text); - const firstLOC = sourceCode.ast.range[0]; - const rangeNeedle = hasUseStrict ? useStrict.lastIndex : firstLOC; - - return fixer.insertTextBeforeRange([rangeNeedle], - `${BUFFER_REQUIRE}\n`); - } - }); - } - - return { - 'Program:exit': function() { - const globalScope = context.getScope(); - const variable = globalScope.set.get('Buffer'); - if (variable) { - variable.references.forEach(flagIt); - } - } - }; -}; diff --git a/tools/eslint-rules/require-globals.js b/tools/eslint-rules/require-globals.js new file mode 100644 index 00000000000000..bc49ff6c8746ed --- /dev/null +++ b/tools/eslint-rules/require-globals.js @@ -0,0 +1,50 @@ +'use strict'; + +// This rule makes sure that no Globals are going to be used in /lib. +// That could otherwise result in problems with the repl. + +module.exports = function(context) { + + function flagIt(msg, fix) { + return (reference) => { + context.report({ + node: reference.identifier, + message: msg, + fix: (fixer) => { + const sourceCode = context.getSourceCode(); + + const useStrict = /'use strict';\n\n?/g; + const hasUseStrict = !!useStrict.exec(sourceCode.text); + const firstLOC = sourceCode.ast.range[0]; + const rangeNeedle = hasUseStrict ? useStrict.lastIndex : firstLOC; + + return fixer.insertTextBeforeRange([rangeNeedle], `${fix}\n`); + } + }); + }; + } + + return { + 'Program:exit': function() { + const globalScope = context.getScope(); + let variable = globalScope.set.get('Buffer'); + if (variable) { + const fix = "const { Buffer } = require('buffer');"; + const msg = `Use ${fix} at the beginning of this file`; + variable.references.forEach(flagIt(msg, fix)); + } + variable = globalScope.set.get('URL'); + if (variable) { + const fix = "const { URL } = require('url');"; + const msg = `Use ${fix} at the beginning of this file`; + variable.references.forEach(flagIt(msg, fix)); + } + variable = globalScope.set.get('URLSearchParams'); + if (variable) { + const fix = "const { URLSearchParams } = require('url');"; + const msg = `Use ${fix} at the beginning of this file`; + variable.references.forEach(flagIt(msg, fix)); + } + } + }; +};