Skip to content

Commit 00dea28

Browse files
watilderichardlau
authored andcommitted
repl: lazy-load acorn and defer vm context creation
Merely loading the repl builtin used to eagerly require acorn and acorn-walk (~250KB of JS) and create an entire V8 context via vm.runInNewContext() just to enumerate global property names, even though both are only needed once REPL input is actually parsed or tab-completion is used. Require acorn and acorn-walk at their function-level use sites instead, and wrap the global builtins set in getLazy(). The cost moves to the first preview/completion/recoverable-error check, where the one-time ~1ms is imperceptible. Benchmark results (Linux x64, misc/startup-core.js, 20 runs): require-builtins.js: +3.12% ops/s (t=3.57, p<0.01) import-builtins.mjs: +2.20% ops/s (t=3.00, p<0.01) In isolation, require('repl') drops from 4.64ms to 2.84ms (-39%) and interactive `node -i` startup improves by ~13%. Signed-off-by: Daijiro Wachi <daijiro.wachi@gmail.com> PR-URL: #63879 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 8ef643d commit 00dea28

3 files changed

Lines changed: 22 additions & 22 deletions

File tree

‎lib/internal/repl/completion.js‎

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const {
3333
const{
3434
kContextId,
3535
getREPLResourceName,
36-
globalBuiltins,
36+
getGlobalBuiltins,
3737
getReplBuiltinLibs,
3838
fixReplRequire,
3939
}=require('internal/repl/utils');
@@ -61,13 +61,6 @@ const {
6161
getOwnNonIndexProperties,
6262
}=internalBinding('util');
6363

64-
const{
65-
isIdentifierStart,
66-
isIdentifierChar,
67-
parse: acornParse,
68-
}=require('internal/deps/acorn/acorn/dist/acorn');
69-
constacornWalk=require('internal/deps/acorn/acorn-walk/dist/walk');
70-
7164
constimportRE=/\bimport\s*\(\s*['"`](([\w@./:-]+\/)?(?:[\w@./:-]*))(?![^'"`])$/;
7265
constrequireRE=/\brequire\s*\(\s*['"`](([\w@./:-]+\/)?(?:[\w@./:-]*))(?![^'"`])$/;
7366
constfsAutoCompleteRE=/fs(?:\.promises)?\.\s*[a-z][a-zA-Z]+\(\s*["'](.*)/;
@@ -87,6 +80,8 @@ function isIdentifier(str) {
8780
if(str===''){
8881
returnfalse;
8982
}
83+
const{ isIdentifierStart, isIdentifierChar }=
84+
require('internal/deps/acorn/acorn/dist/acorn');
9085
constfirst=StringPrototypeCodePointAt(str,0);
9186
if(!isIdentifierStart(first)){
9287
returnfalse;
@@ -374,8 +369,8 @@ function complete(line, callback) {
374369
if(!this.useGlobal){
375370
// When the context is not `global`, builtins are not own
376371
// properties of it.
377-
// `globalBuiltins` is a `SafeSet`, not an Array-like.
378-
ArrayPrototypePush(contextOwnNames, ...globalBuiltins);
372+
// `getGlobalBuiltins()` is a `SafeSet`, not an Array-like.
373+
ArrayPrototypePush(contextOwnNames, ...getGlobalBuiltins());
379374
}
380375
ArrayPrototypePush(completionGroups,contextOwnNames);
381376
if(filter!=='')addCommonWords(completionGroups);
@@ -387,6 +382,7 @@ function complete(line, callback) {
387382
// so in order to make it correct we add an identifier to its end (e.g. `obj.foo.x`)
388383
constparsableCompleteTarget=completeTarget.endsWith('.') ? `${completeTarget}x` : completeTarget;
389384

385+
const{parse: acornParse}=require('internal/deps/acorn/acorn/dist/acorn');
390386
letcompleteTargetAst;
391387
try{
392388
completeTargetAst=acornParse(
@@ -551,6 +547,7 @@ function findExpressionCompleteTarget(code) {
551547
return!result ? result : `${result}.`;
552548
}
553549

550+
const{parse: acornParse}=require('internal/deps/acorn/acorn/dist/acorn');
554551
letast;
555552
try{
556553
ast=acornParse(code,{__proto__: null,sourceType: 'module',ecmaVersion: 'latest'});
@@ -625,6 +622,7 @@ function findExpressionCompleteTarget(code) {
625622

626623
// Walk the AST for the current block of code, and check whether it contains any
627624
// statement or expression type that would potentially have side effects if evaluated.
625+
constacornWalk=require('internal/deps/acorn/acorn-walk/dist/walk');
628626
letisAllowed=true;
629627
constdisallow=()=>isAllowed=false;
630628
acornWalk.simple(lastBodyStatement,{

‎lib/internal/repl/utils.js‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,8 @@ const {
2020
Symbol,
2121
}=primordials;
2222

23-
const{tokTypes: tt,Parser: AcornParser}=
24-
require('internal/deps/acorn/acorn/dist/acorn');
25-
2623
const{ sendInspectorCommand }=require('internal/util/inspector');
24+
const{ getLazy }=require('internal/util');
2725

2826
const{
2927
ERR_INSPECTOR_NOT_AVAILABLE,
@@ -80,6 +78,9 @@ function isRecoverableError(e, code) {
8078
isRecoverableError(e,`(${code}`))
8179
returntrue;
8280

81+
const{tokTypes: tt,Parser: AcornParser}=
82+
require('internal/deps/acorn/acorn/dist/acorn');
83+
8384
letrecoverable=false;
8485

8586
// Determine if the point of any error raised is at the end of the input.
@@ -756,6 +757,8 @@ function setupReverseSearch(repl) {
756757
conststartsWithBraceRegExp=/^\s*{/;
757758
constendsWithSemicolonRegExp=/;\s*$/;
758759
functionisValidSyntax(input){
760+
const{Parser: AcornParser}=
761+
require('internal/deps/acorn/acorn/dist/acorn');
759762
try{
760763
AcornParser.parse(input,{
761764
ecmaVersion: 'latest',
@@ -815,8 +818,9 @@ function getREPLResourceName() {
815818
return`REPL${nextREPLResourceNumber++}`;
816819
}
817820

818-
constglobalBuiltins=
819-
newSafeSet(vm.runInNewContext('Object.getOwnPropertyNames(globalThis)'));
821+
// Creating a new context is expensive, so only do it on first use.
822+
constgetGlobalBuiltins=getLazy(()=>
823+
newSafeSet(vm.runInNewContext('Object.getOwnPropertyNames(globalThis)')));
820824

821825
let_builtinLibs=ArrayPrototypeFilter(
822826
CJSModule.builtinModules,
@@ -848,7 +852,7 @@ module.exports = {
848852
isValidSyntax,
849853
kContextId,
850854
getREPLResourceName,
851-
globalBuiltins,
855+
getGlobalBuiltins,
852856
getReplBuiltinLibs,
853857
setReplBuiltinLibs,
854858
fixReplRequire,

‎lib/repl.js‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,6 @@ const {
8989
makeRequireFunction,
9090
addBuiltinLibsToObject,
9191
}=require('internal/modules/helpers');
92-
const{
93-
parse: acornParse,
94-
}=require('internal/deps/acorn/acorn/dist/acorn');
95-
constacornWalk=require('internal/deps/acorn/acorn-walk/dist/walk');
9692
const{
9793
decorateErrorStack,
9894
isError,
@@ -153,7 +149,7 @@ const {
153149
isValidSyntax,
154150
kContextId,
155151
getREPLResourceName,
156-
globalBuiltins,
152+
getGlobalBuiltins,
157153
getReplBuiltinLibs,
158154
setReplBuiltinLibs,
159155
fixReplRequire,
@@ -249,6 +245,8 @@ writer.options = { ...inspect.defaultOptions, showProxy: true };
249245

250246
// Converts static import statement to dynamic import statement
251247
consttoDynamicImport=(codeLine)=>{
248+
const{parse: acornParse}=require('internal/deps/acorn/acorn/dist/acorn');
249+
constacornWalk=require('internal/deps/acorn/acorn-walk/dist/walk');
252250
letdynamicImportStatement='';
253251
constast=acornParse(codeLine,{__proto__: null,sourceType: 'module',ecmaVersion: 'latest'});
254252
acornWalk.ancestor(ast,{
@@ -1139,7 +1137,7 @@ class REPLServer extends Interface {
11391137
});
11401138
ArrayPrototypeForEach(ObjectGetOwnPropertyNames(globalThis),(name)=>{
11411139
// Only set properties that do not already exist as a global builtin.
1142-
if(!globalBuiltins.has(name)){
1140+
if(!getGlobalBuiltins().has(name)){
11431141
ObjectDefineProperty(context,name,
11441142
{
11451143
__proto__: null,

0 commit comments

Comments
 (0)