Skip to content

Commit 66f52f5

Browse files
committed
feat: preload function for Environment
This PR provides an API in node::Environment to allow embedders to set a preload function for environment, which will run after the environment is loaded and before the main script runs. This is similiar to the --require CLI option, but runs a C++ function, and can only be set by embedders. The preload function can be used by embedders to inject scripts before running the main script, for example: 1. In Electron it is used to initialize the ASAR virtual filesystem, inject custom process properties, etc. 2. In VS Code it can be used to reset the locations where an extension can search for modules.
1 parent 27d839f commit 66f52f5

File tree

7 files changed

+48
-0
lines changed

7 files changed

+48
-0
lines changed

lib/internal/process/pre_execution.js

+7
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,9 @@ function setupUserModules(forceDefaultLoader = false) {
186186
initializeESMLoader(forceDefaultLoader);
187187
const CJSLoader = require('internal/modules/cjs/loader');
188188
assert(!CJSLoader.hasLoadedAnyUserCJSModule);
189+
if (getEmbedderOptions().hasEmbedderPreload) {
190+
runEmbedderPreload();
191+
}
189192
// Do not enable preload modules if custom loaders are disabled.
190193
// For example, loader workers are responsible for doing this themselves.
191194
// And preload modules are not supported in ShadowRealm as well.
@@ -754,6 +757,10 @@ function initializeFrozenIntrinsics() {
754757
}
755758
}
756759

760+
function runEmbedderPreload() {
761+
internalBinding('mksnapshot').runEmbedderPreload(process, require);
762+
}
763+
757764
function loadPreloadModules() {
758765
// For user code, we preload modules if `-r` is passed
759766
const preloadModules = getOptionValue('--require');

src/env-inl.h

+8
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,14 @@ inline void Environment::set_embedder_entry_point(StartExecutionCallback&& fn) {
438438
embedder_entry_point_ = std::move(fn);
439439
}
440440

441+
inline const EmbedderPreloadCallback& Environment::embedder_preload() const {
442+
return embedder_preload_;
443+
}
444+
445+
inline void Environment::set_embedder_preload(EmbedderPreloadCallback&& fn) {
446+
embedder_preload_ = std::move(fn);
447+
}
448+
441449
inline double Environment::new_async_id() {
442450
async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter] += 1;
443451
return async_hooks()->async_id_fields()[AsyncHooks::kAsyncIdCounter];

src/env.h

+15
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,11 @@ void DefaultProcessExitHandlerInternal(Environment* env, ExitCode exit_code);
588588
v8::Maybe<ExitCode> SpinEventLoopInternal(Environment* env);
589589
v8::Maybe<ExitCode> EmitProcessExitInternal(Environment* env);
590590

591+
using EmbedderPreloadCallback =
592+
std::function<void(Environment* env,
593+
v8::Local<v8::Value> process,
594+
v8::Local<v8::Value> require)>;
595+
591596
/**
592597
* Environment is a per-isolate data structure that represents an execution
593598
* environment. Each environment has a principal realm. An environment can
@@ -1002,6 +1007,15 @@ class Environment : public MemoryRetainer {
10021007
inline const StartExecutionCallback& embedder_entry_point() const;
10031008
inline void set_embedder_entry_point(StartExecutionCallback&& fn);
10041009

1010+
// Set a preload function that will run before executing the entry point, this
1011+
// is usually used by embedders to inject scripts.
1012+
// The function is executed with preload(process, require), and the passed
1013+
// |require| function has access to internal Node.js modules.
1014+
// The preload function is inherited by worker threads and thus will run in
1015+
// work threads, so make sure the function is thread-safe.
1016+
inline const EmbedderPreloadCallback& embedder_preload() const;
1017+
inline void set_embedder_preload(EmbedderPreloadCallback&& fn);
1018+
10051019
inline void set_process_exit_handler(
10061020
std::function<void(Environment*, ExitCode)>&& handler);
10071021

@@ -1208,6 +1222,7 @@ class Environment : public MemoryRetainer {
12081222

12091223
builtins::BuiltinLoader builtin_loader_;
12101224
StartExecutionCallback embedder_entry_point_;
1225+
EmbedderPreloadCallback embedder_preload_;
12111226

12121227
// Used by allocate_managed_buffer() and release_managed_buffer() to keep
12131228
// track of the BackingStore for a given pointer.

src/node_options.cc

+6
Original file line numberDiff line numberDiff line change
@@ -1304,6 +1304,12 @@ void GetEmbedderOptions(const FunctionCallbackInfo<Value>& args) {
13041304
.IsNothing())
13051305
return;
13061306

1307+
if (ret->Set(context,
1308+
FIXED_ONE_BYTE_STRING(env->isolate(), "hasEmbedderPreload"),
1309+
Boolean::New(isolate, env->embedder_preload() != nullptr))
1310+
.IsNothing())
1311+
return;
1312+
13071313
args.GetReturnValue().Set(ret);
13081314
}
13091315

src/node_snapshotable.cc

+9
Original file line numberDiff line numberDiff line change
@@ -1453,6 +1453,13 @@ static void RunEmbedderEntryPoint(const FunctionCallbackInfo<Value>& args) {
14531453
}
14541454
}
14551455

1456+
static void RunEmbedderPreload(const FunctionCallbackInfo<Value>& args) {
1457+
Environment* env = Environment::GetCurrent(args);
1458+
CHECK(env->embedder_preload());
1459+
CHECK_EQ(args.Length(), 2);
1460+
env->embedder_preload()(env, args[0], args[1]);
1461+
}
1462+
14561463
void CompileSerializeMain(const FunctionCallbackInfo<Value>& args) {
14571464
CHECK(args[0]->IsString());
14581465
Local<String> filename = args[0].As<String>();
@@ -1577,6 +1584,7 @@ void CreatePerIsolateProperties(IsolateData* isolate_data,
15771584
Local<ObjectTemplate> target) {
15781585
Isolate* isolate = isolate_data->isolate();
15791586
SetMethod(isolate, target, "runEmbedderEntryPoint", RunEmbedderEntryPoint);
1587+
SetMethod(isolate, target, "runEmbedderPreload", RunEmbedderPreload);
15801588
SetMethod(isolate, target, "compileSerializeMain", CompileSerializeMain);
15811589
SetMethod(isolate, target, "setSerializeCallback", SetSerializeCallback);
15821590
SetMethod(isolate, target, "setDeserializeCallback", SetDeserializeCallback);
@@ -1590,6 +1598,7 @@ void CreatePerIsolateProperties(IsolateData* isolate_data,
15901598

15911599
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
15921600
registry->Register(RunEmbedderEntryPoint);
1601+
registry->Register(RunEmbedderPreload);
15931602
registry->Register(CompileSerializeMain);
15941603
registry->Register(SetSerializeCallback);
15951604
registry->Register(SetDeserializeCallback);

src/node_worker.cc

+2
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Worker::Worker(Environment* env,
6363
thread_id_(AllocateEnvironmentThreadId()),
6464
name_(name),
6565
env_vars_(env_vars),
66+
embedder_preload_(env->embedder_preload()),
6667
snapshot_data_(snapshot_data) {
6768
Debug(this, "Creating new worker instance with thread id %llu",
6869
thread_id_.id);
@@ -370,6 +371,7 @@ void Worker::Run() {
370371
if (is_stopped()) return;
371372
CHECK_NOT_NULL(env_);
372373
env_->set_env_vars(std::move(env_vars_));
374+
env_->set_embedder_preload(std::move(embedder_preload_));
373375
SetProcessExitHandler(env_.get(), [this](Environment*, int exit_code) {
374376
Exit(static_cast<ExitCode>(exit_code));
375377
});

src/node_worker.h

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ class Worker : public AsyncWrap {
114114

115115
std::unique_ptr<MessagePortData> child_port_data_;
116116
std::shared_ptr<KVStore> env_vars_;
117+
EmbedderPreloadCallback embedder_preload_;
117118

118119
// A raw flag that is used by creator and worker threads to
119120
// sync up on pre-mature termination of worker - while in the

0 commit comments

Comments
 (0)