Skip to content

Commit

Permalink
bootstrap: lazy load non-essential modules
Browse files Browse the repository at this point in the history
It turns out that even with startup snapshots, there is a non-trivial
overhead for loading internal modules. This patch makes the loading
of the non-essential modules lazy again.

Caveat: we have to make some of the globals lazily-loaded too,
so the WPT runner is updated to test what the state of the global
scope is after the globals are accessed (and replaced with the
loaded value).

PR-URL: #45659
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Daeyeon Jeong <daeyeon.dev@gmail.com>
Reviewed-By: Jacob Smith <jacob@frende.me>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Minwoo Jung <nodecorelab@gmail.com>
Reviewed-By: Tierney Cyren <hello@bnb.im>
  • Loading branch information
joyeecheung authored and targos committed Dec 13, 2022
1 parent 75dbce9 commit e2caf7c
Show file tree
Hide file tree
Showing 26 changed files with 321 additions and 367 deletions.
26 changes: 13 additions & 13 deletions lib/buffer.js
Expand Up @@ -78,7 +78,8 @@ const {
isInsideNodeModules,
lazyDOMException,
normalizeEncoding,
kIsEncodingSymbol
kIsEncodingSymbol,
defineLazyProperties,
} = require('internal/util');
const {
isAnyArrayBuffer,
Expand Down Expand Up @@ -121,15 +122,6 @@ const {
createUnsafeBuffer
} = require('internal/buffer');

const {
Blob,
resolveObjectURL,
} = require('internal/blob');

const {
File,
} = require('internal/file');

FastBuffer.prototype.constructor = Buffer;
Buffer.prototype = FastBuffer.prototype;
addBufferPrototypeMethods(Buffer.prototype);
Expand Down Expand Up @@ -1323,9 +1315,6 @@ function atob(input) {
}

module.exports = {
Blob,
File,
resolveObjectURL,
Buffer,
SlowBuffer,
transcode,
Expand All @@ -1352,3 +1341,14 @@ ObjectDefineProperties(module.exports, {
set(val) { INSPECT_MAX_BYTES = val; }
}
});

defineLazyProperties(
module.exports,
'internal/blob',
['Blob', 'resolveObjectURL']
);
defineLazyProperties(
module.exports,
'internal/file',
['File']
);
27 changes: 12 additions & 15 deletions lib/fs.js
Expand Up @@ -57,7 +57,6 @@ const {

const pathModule = require('path');
const { isArrayBufferView } = require('internal/util/types');
const nonNativeWatcher = require('internal/fs/recursive_watch');

// We need to get the statValues from the binding at the callsite since
// it's re-initialized after deserialization.
Expand All @@ -84,6 +83,7 @@ const {
custom: kCustomPromisifiedSymbol,
},
SideEffectFreeRegExpPrototypeExec,
defineLazyProperties,
} = require('internal/util');
const {
constants: {
Expand Down Expand Up @@ -119,11 +119,6 @@ const {
validateStringAfterArrayBufferView,
warnOnNonPortableTemplate
} = require('internal/fs/utils');
const {
Dir,
opendir,
opendirSync
} = require('internal/fs/dir');
const {
CHAR_FORWARD_SLASH,
CHAR_BACKWARD_SLASH,
Expand All @@ -140,9 +135,6 @@ const {
validateString,
} = require('internal/validators');

const watchers = require('internal/fs/watchers');
const ReadFileContext = require('internal/fs/read_file_context');

let truncateWarn = true;
let fs;

Expand Down Expand Up @@ -379,6 +371,7 @@ function checkAborted(signal, callback) {
function readFile(path, options, callback) {
callback = maybeCallback(callback || options);
options = getOptions(options, { flag: 'r' });
const ReadFileContext = require('internal/fs/read_file_context');
const context = new ReadFileContext(callback, options.encoding);
context.isUserFd = isFd(path); // File descriptor ownership

Expand Down Expand Up @@ -2298,11 +2291,12 @@ function watch(filename, options, listener) {
if (options.recursive === undefined) options.recursive = false;

let watcher;

const watchers = require('internal/fs/watchers');
// TODO(anonrig): Remove non-native watcher when/if libuv supports recursive.
// As of November 2022, libuv does not support recursive file watch on all platforms,
// e.g. Linux due to the limitations of inotify.
if (options.recursive && !isOSX && !isWindows) {
const nonNativeWatcher = require('internal/fs/recursive_watch');
watcher = new nonNativeWatcher.FSWatcher(options);
watcher[watchers.kFSWatchStart](filename);
} else {
Expand Down Expand Up @@ -2370,7 +2364,7 @@ function watchFile(filename, options, listener) {
validateFunction(listener, 'listener');

stat = statWatchers.get(filename);

const watchers = require('internal/fs/watchers');
if (stat === undefined) {
stat = new watchers.StatWatcher(options.bigint);
stat[watchers.kFSStatWatcherStart](filename,
Expand All @@ -2396,7 +2390,7 @@ function unwatchFile(filename, listener) {
const stat = statWatchers.get(filename);

if (stat === undefined) return;

const watchers = require('internal/fs/watchers');
if (typeof listener === 'function') {
const beforeListenerCount = stat.listenerCount('change');
stat.removeListener('change', listener);
Expand Down Expand Up @@ -3000,8 +2994,6 @@ module.exports = fs = {
mkdtempSync,
open,
openSync,
opendir,
opendirSync,
readdir,
readdirSync,
read,
Expand Down Expand Up @@ -3039,7 +3031,6 @@ module.exports = fs = {
writeSync,
writev,
writevSync,
Dir,
Dirent,
Stats,

Expand Down Expand Up @@ -3085,6 +3076,12 @@ module.exports = fs = {
_toUnixTimestamp: toUnixTimestamp
};

defineLazyProperties(
fs,
'internal/fs/dir',
['Dir', 'opendir', 'opendirSync']
);

ObjectDefineProperties(fs, {
F_OK: { __proto__: null, enumerable: true, value: F_OK || 0 },
R_OK: { __proto__: null, enumerable: true, value: R_OK || 0 },
Expand Down
3 changes: 1 addition & 2 deletions lib/internal/async_hooks.js
Expand Up @@ -10,8 +10,6 @@ const {

const { exitCodes: { kGenericUserError } } = internalBinding('errors');

const promiseHooks = require('internal/promise_hooks');

const async_wrap = internalBinding('async_wrap');
const { setCallbackTrampoline } = async_wrap;
/* async_hook_fields is a Uint32Array wrapping the uint32_t array of
Expand Down Expand Up @@ -384,6 +382,7 @@ function updatePromiseHookMode() {
initHook = destroyTracking;
}
if (stopPromiseHook) stopPromiseHook();
const promiseHooks = require('internal/promise_hooks');
stopPromiseHook = promiseHooks.createHook({
init: initHook,
before: promiseBeforeHook,
Expand Down
186 changes: 62 additions & 124 deletions lib/internal/bootstrap/browser.js
Expand Up @@ -9,6 +9,9 @@ const {
defineOperation,
exposeInterface,
lazyDOMExceptionClass,
defineLazyProperties,
defineReplaceableLazyAttribute,
exposeLazyInterfaces,
} = require('internal/util');
const config = internalBinding('config');

Expand All @@ -28,61 +31,39 @@ exposeGetterAndSetter(globalThis,
exposeInterface(globalThis, 'DOMException', value);
});

const {
TextEncoder, TextDecoder
} = require('internal/encoding');
// https://encoding.spec.whatwg.org/#textencoder
exposeInterface(globalThis, 'TextEncoder', TextEncoder);
// https://encoding.spec.whatwg.org/#textdecoder
exposeInterface(globalThis, 'TextDecoder', TextDecoder);

const {
AbortController,
AbortSignal,
} = require('internal/abort_controller');
exposeInterface(globalThis, 'AbortController', AbortController);
exposeInterface(globalThis, 'AbortSignal', AbortSignal);

const {
EventTarget,
Event,
} = require('internal/event_target');
exposeInterface(globalThis, 'EventTarget', EventTarget);
exposeInterface(globalThis, 'Event', Event);
const {
MessageChannel,
MessagePort,
MessageEvent,
} = require('internal/worker/io');
exposeInterface(globalThis, 'MessageChannel', MessageChannel);
exposeInterface(globalThis, 'MessagePort', MessagePort);
exposeInterface(globalThis, 'MessageEvent', MessageEvent);

// https://html.spec.whatwg.org/multipage/webappapis.html#windoworworkerglobalscope
const timers = require('timers');
defineOperation(globalThis, 'clearInterval', timers.clearInterval);
defineOperation(globalThis, 'clearTimeout', timers.clearTimeout);
defineOperation(globalThis, 'setInterval', timers.setInterval);
defineOperation(globalThis, 'setTimeout', timers.setTimeout);

const buffer = require('buffer');
defineOperation(globalThis, 'atob', buffer.atob);
defineOperation(globalThis, 'btoa', buffer.btoa);

// Lazy ones.
exposeLazyInterfaces(globalThis, 'internal/abort_controller', [
'AbortController', 'AbortSignal',
]);
exposeLazyInterfaces(globalThis, 'internal/event_target', [
'EventTarget', 'Event',
]);
exposeLazyInterfaces(globalThis, 'internal/worker/io', [
'MessageChannel', 'MessagePort', 'MessageEvent',
]);
defineLazyProperties(globalThis, 'buffer', ['atob', 'btoa']);
// https://www.w3.org/TR/FileAPI/#dfn-Blob
exposeInterface(globalThis, 'Blob', buffer.Blob);

exposeLazyInterfaces(globalThis, 'internal/blob', ['Blob']);
// https://www.w3.org/TR/hr-time-2/#the-performance-attribute
const perf_hooks = require('perf_hooks');
exposeInterface(globalThis, 'Performance', perf_hooks.Performance);
exposeInterface(globalThis, 'PerformanceEntry', perf_hooks.PerformanceEntry);
exposeInterface(globalThis, 'PerformanceMark', perf_hooks.PerformanceMark);
exposeInterface(globalThis, 'PerformanceMeasure', perf_hooks.PerformanceMeasure);
exposeInterface(globalThis, 'PerformanceObserver', perf_hooks.PerformanceObserver);
exposeInterface(globalThis, 'PerformanceObserverEntryList', perf_hooks.PerformanceObserverEntryList);
exposeInterface(globalThis, 'PerformanceResourceTiming', perf_hooks.PerformanceResourceTiming);
defineReplaceableAttribute(globalThis, 'performance',
perf_hooks.performance);
exposeLazyInterfaces(globalThis, 'perf_hooks', [
'Performance', 'PerformanceEntry', 'PerformanceMark', 'PerformanceMeasure',
'PerformanceObserver', 'PerformanceObserverEntryList', 'PerformanceResourceTiming',
]);

defineReplaceableLazyAttribute(globalThis, 'perf_hooks', ['performance']);

// https://encoding.spec.whatwg.org/#textencoder
// https://encoding.spec.whatwg.org/#textdecoder
exposeLazyInterfaces(globalThis,
'internal/encoding',
['TextEncoder', 'TextDecoder']);

function createGlobalConsole() {
const consoleFromNode =
Expand Down Expand Up @@ -120,86 +101,43 @@ function exposeGetterAndSetter(target, name, getter, setter = undefined) {
});
}

// https://webidl.spec.whatwg.org/#Replaceable
function defineReplaceableAttribute(target, name, value) {
let slot = value;

// https://webidl.spec.whatwg.org/#dfn-attribute-getter
function get() {
return slot;
}
ObjectDefineProperty(get, 'name', {
__proto__: null,
value: `get ${name}`,
});

function set(value) {
slot = value;
}
ObjectDefineProperty(set, 'name', {
__proto__: null,
value: `set ${name}`,
});

ObjectDefineProperty(target, name, {
__proto__: null,
enumerable: true,
configurable: true,
get,
set,
});
}

// Web Streams API
const {
TransformStream,
TransformStreamDefaultController,
} = require('internal/webstreams/transformstream');

const {
WritableStream,
WritableStreamDefaultController,
WritableStreamDefaultWriter,
} = require('internal/webstreams/writablestream');
exposeLazyInterfaces(
globalThis,
'internal/webstreams/transformstream',
['TransformStream', 'TransformStreamDefaultController']);

const {
ReadableStream,
ReadableStreamDefaultReader,
ReadableStreamBYOBReader,
ReadableStreamBYOBRequest,
ReadableByteStreamController,
ReadableStreamDefaultController,
} = require('internal/webstreams/readablestream');
exposeLazyInterfaces(
globalThis,
'internal/webstreams/writablestream',
['WritableStream', 'WritableStreamDefaultController', 'WritableStreamDefaultWriter']);

const {
ByteLengthQueuingStrategy,
CountQueuingStrategy,
} = require('internal/webstreams/queuingstrategies');
exposeLazyInterfaces(
globalThis,
'internal/webstreams/readablestream',
[
'ReadableStream', 'ReadableStreamDefaultReader',
'ReadableStreamBYOBReader', 'ReadableStreamBYOBRequest',
'ReadableByteStreamController', 'ReadableStreamDefaultController',
]);

exposeLazyInterfaces(
globalThis,
'internal/webstreams/queuingstrategies',
[
'ByteLengthQueuingStrategy', 'CountQueuingStrategy',
]);

const {
TextEncoderStream,
TextDecoderStream,
} = require('internal/webstreams/encoding');
exposeLazyInterfaces(
globalThis,
'internal/webstreams/encoding',
[
'TextEncoderStream', 'TextDecoderStream',
]);

const {
CompressionStream,
DecompressionStream,
} = require('internal/webstreams/compression');

exposeInterface(globalThis, 'ReadableStream', ReadableStream);
exposeInterface(globalThis, 'ReadableStreamDefaultReader', ReadableStreamDefaultReader);
exposeInterface(globalThis, 'ReadableStreamBYOBReader', ReadableStreamBYOBReader);
exposeInterface(globalThis, 'ReadableStreamBYOBRequest', ReadableStreamBYOBRequest);
exposeInterface(globalThis, 'ReadableByteStreamController', ReadableByteStreamController);
exposeInterface(globalThis, 'ReadableStreamDefaultController', ReadableStreamDefaultController);
exposeInterface(globalThis, 'TransformStream', TransformStream);
exposeInterface(globalThis, 'TransformStreamDefaultController', TransformStreamDefaultController);
exposeInterface(globalThis, 'WritableStream', WritableStream);
exposeInterface(globalThis, 'WritableStreamDefaultWriter', WritableStreamDefaultWriter);
exposeInterface(globalThis, 'WritableStreamDefaultController', WritableStreamDefaultController);
exposeInterface(globalThis, 'ByteLengthQueuingStrategy', ByteLengthQueuingStrategy);
exposeInterface(globalThis, 'CountQueuingStrategy', CountQueuingStrategy);
exposeInterface(globalThis, 'TextEncoderStream', TextEncoderStream);
exposeInterface(globalThis, 'TextDecoderStream', TextDecoderStream);
exposeInterface(globalThis, 'CompressionStream', CompressionStream);
exposeInterface(globalThis, 'DecompressionStream', DecompressionStream);
exposeLazyInterfaces(
globalThis,
'internal/webstreams/compression',
[
'CompressionStream', 'DecompressionStream',
]);

0 comments on commit e2caf7c

Please sign in to comment.