Skip to content

Commit a0bffea

Browse files
committed
Refinements on the new module registry implementation
1 parent 3c552f6 commit a0bffea

File tree

13 files changed

+271
-233
lines changed

13 files changed

+271
-233
lines changed

src/workerd/api/global-scope.c++

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -870,47 +870,36 @@ void ServiceWorkerGlobalScope::reportError(jsg::Lock& js, jsg::JsValue error) {
870870
}
871871
}
872872

873-
namespace {
874-
jsg::JsValue resolveFromRegistry(jsg::Lock& js, kj::StringPtr specifier) {
875-
auto moduleRegistry = jsg::ModuleRegistry::from(js);
876-
if (moduleRegistry == nullptr) {
877-
// TODO: Return the known built-in instead? This gets a bit tricky as we currently
878-
// have no mechanism defined for accessing and caching the built-in module without
879-
// the module registry. Should we even support this case at all? Without the module
880-
// registry the user can't access the other importable modules anyway. For now, just
881-
// returning undefined in this case seems the most appropriate.
882-
return js.undefined();
883-
}
884-
885-
auto spec = kj::Path::parse(specifier);
886-
auto& info = JSG_REQUIRE_NONNULL(
887-
moduleRegistry->resolve(js, spec), Error, kj::str("No such module: ", specifier));
888-
auto module = info.module.getHandle(js);
889-
890-
jsg::instantiateModule(js, module);
891-
return jsg::JsValue(js.v8Get(module->GetModuleNamespace().As<v8::Object>(), "default"_kj));
892-
}
893-
} // namespace
894-
895873
jsg::JsValue ServiceWorkerGlobalScope::getBuffer(jsg::Lock& js) {
896874
KJ_ASSERT(FeatureFlags::get(js).getNodeJsCompatV2());
897-
auto value = resolveFromRegistry(js, "node:buffer"_kj);
898-
auto obj = JSG_REQUIRE_NONNULL(
899-
value.tryCast<jsg::JsObject>(), TypeError, "Invalid node:buffer implementation");
900-
// Unlike the getProcess() case below, this getter is returning an object that
901-
// is exported by the node:buffer module and not the module itself, so we need
902-
// this additional get to the grab the reference to the thing we're actually
903-
// returning.
904-
auto buffer = obj.get(js, "Buffer"_kj);
905-
JSG_REQUIRE(buffer.isFunction(), TypeError, "Invalid node:buffer implementation");
906-
return buffer;
875+
static const auto kSpecifier = "node:buffer"_kj;
876+
KJ_IF_SOME(module, js.resolveModule(kSpecifier)) {
877+
auto def = module.get(js, "default"_kj);
878+
auto obj = KJ_ASSERT_NONNULL(def.tryCast<jsg::JsObject>());
879+
auto buffer = obj.get(js, "Buffer"_kj);
880+
JSG_REQUIRE(buffer.isFunction(), TypeError, "Invalid node:buffer implementation");
881+
return buffer;
882+
} else {
883+
// If we are unable to resolve the node:buffer module here, it likely
884+
// means that we don't actually have a module registry installed. Just
885+
// return undefined in this case.
886+
return js.undefined();
887+
}
907888
}
908889

909890
jsg::JsValue ServiceWorkerGlobalScope::getProcess(jsg::Lock& js) {
910891
KJ_ASSERT(FeatureFlags::get(js).getNodeJsCompatV2());
911-
auto value = resolveFromRegistry(js, "node:process"_kj);
912-
JSG_REQUIRE(value.isObject(), TypeError, "Invalid node:process implementation");
913-
return value;
892+
static const auto kSpecifier = "node:process"_kj;
893+
KJ_IF_SOME(module, js.resolveModule(kSpecifier)) {
894+
auto def = module.get(js, "default"_kj);
895+
JSG_REQUIRE(def.isObject(), TypeError, "Invalid node:process implementation");
896+
return def;
897+
} else {
898+
// If we are unable to resolve the node:process module here, it likely
899+
// means that we don't actually have a module registry installed. Just
900+
// return undefined in this case.
901+
return js.undefined();
902+
}
914903
}
915904

916905
double Performance::now() {

src/workerd/api/tests/new-module-registry-test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,20 @@ strictEqual(import.meta.url, 'file:///worker');
1616
// Verify that import.meta.main is true here.
1717
ok(import.meta.main);
1818

19+
// When running in nodejs_compat_v2 mode, the globalThis.Buffer
20+
// and globalThis.process properties are resolved from the module
21+
// registry. Let's make sure we get good values here.
22+
strictEqual(typeof globalThis.Buffer, 'function');
23+
strictEqual(globalThis.Buffer, Buffer);
24+
ok(globalThis.process);
25+
strictEqual(typeof globalThis.process, 'object');
26+
27+
// Our internal implementation of console.log depend on the module registry
28+
// to get the node-internal:internal_inspect module. This console.log makes
29+
// sure that works correctly without crashing when using the new module
30+
// registry implementation.
31+
console.log(import.meta);
32+
1933
// Verify that import.meta.resolve provides correct results here.
2034
// The input should be interpreted as a URL and normalized according
2135
// to the rules in the WHATWG URL specification.
@@ -46,6 +60,11 @@ strictEqual(Buffer.from(data.default).toString(), 'abcdef');
4660
import * as json from 'json';
4761
deepStrictEqual(json.default, { foo: 1 });
4862

63+
// Synchronously resolved promises can be awaited.
64+
await Promise.resolve();
65+
66+
// These dynamics imports can be top-level awaited because they
67+
// are immediately rejected with errors.
4968
await rejects(import('invalid-json'), {
5069
message: /Unexpected non-whitespace character after JSON/,
5170
});

src/workerd/io/worker.c++

Lines changed: 28 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,9 @@ struct Worker::Script::Impl {
799799
using DynamicImportHandler = kj::Function<jsg::Value()>;
800800

801801
void configureDynamicImports(jsg::Lock& js, jsg::ModuleRegistry& modules) {
802+
// This is only used with the original module registry implementation.
803+
KJ_ASSERT(!FeatureFlags::get(js).getNewModuleRegistry(),
804+
"legacy dynamic imports must not be used with the new module registry");
802805
static auto constexpr handleDynamicImport =
803806
[](kj::Own<const Worker> worker, DynamicImportHandler handler,
804807
kj::Maybe<jsg::Ref<jsg::AsyncContextFrame>> asyncContext)
@@ -1266,9 +1269,9 @@ Worker::Script::Script(kj::Own<const Isolate> isolateParam,
12661269
context = mContext.getHandle(lock);
12671270
recordedLock.setupContext(context);
12681271
} else {
1269-
// Although we're going to compile a script independent of context, V8 requires that there be
1270-
// an active context, otherwise it will segfault, I guess. So we create a dummy context.
1271-
// (Undocumented, as usual.)
1272+
// Although we're going to compile a script independent of context, V8 requires that
1273+
// there be an active context, otherwise it will segfault, I guess. So we create a
1274+
// dummy context. (Undocumented, as usual.)
12721275
context =
12731276
v8::Context::New(lock.v8Isolate, nullptr, v8::ObjectTemplate::New(lock.v8Isolate));
12741277
// We need to set the highest used index in every context we create to be a nullptr
@@ -1284,32 +1287,32 @@ Worker::Script::Script(kj::Own<const Isolate> isolateParam,
12841287
if (logNewScript) {
12851288
// HACK: Log a message indicating that a new script was loaded. This is used only when the
12861289
// inspector is enabled. We want to do this immediately after the context is created,
1287-
// before the user gets a chance to modify the behavior of the console, which if they did,
1288-
// we'd then need to be more careful to apply time limits and such.
1290+
// before the user gets a chance to modify the behavior of the console, which if they
1291+
// did, we'd then need to be more careful to apply time limits and such.
12891292
lockedWorkerIsolate.logMessage(lock, static_cast<uint16_t>(cdp::LogType::WARNING),
12901293
"Script modified; context reset.");
12911294
}
12921295

1293-
// We need to register this context with the inspector, otherwise errors won't be reported. But
1294-
// we want it to be un-registered as soon as the script has been compiled, otherwise the
1295-
// inspector will end up with multiple contexts active which is very confusing for the user
1296-
// (since they'll have to select from the drop-down which context to use).
1296+
// We need to register this context with the inspector, otherwise errors won't be
1297+
// reported. But we want it to be un-registered as soon as the script has been
1298+
// compiled, otherwise the inspector will end up with multiple contexts active which
1299+
// is very confusing for the user (since they'll have to select from the drop-down
1300+
// which context to use).
12971301
//
12981302
// (For modules, the context was already registered by `setupContext()`, above.
12991303
KJ_IF_SOME(i, isolate->impl->inspector) {
1300-
if (!source.is<ModulesSource>()) {
1304+
if (!modular) {
13011305
i.get()->contextCreated(
13021306
v8_inspector::V8ContextInfo(context, 1, jsg::toInspectorStringView("Compiler")));
13031307
}
13041308
} else {
13051309
} // Here to squash a compiler warning
13061310
KJ_DEFER({
1307-
if (!source.is<ModulesSource>()) {
1311+
if (!modular) {
13081312
KJ_IF_SOME(i, isolate->impl->inspector) {
13091313
i.get()->contextDestroyed(context);
13101314
} else {
1311-
// Else block to avoid dangling else clang warning.
1312-
}
1315+
} // Here to squash a compiler warning
13131316
}
13141317
});
13151318

@@ -1320,23 +1323,26 @@ Worker::Script::Script(kj::Own<const Isolate> isolateParam,
13201323
try {
13211324
KJ_SWITCH_ONEOF(source) {
13221325
KJ_CASE_ONEOF(script, ScriptSource) {
1326+
// This path is used for the older, service worker syntax workers.
1327+
13231328
impl->globals =
13241329
script.compileGlobals(lock, isolate->getApi(), isolate->getApi().getObserver());
13251330

13261331
{
1327-
// It's unclear to me if CompileUnboundScript() can get trapped in any infinite loops or
1328-
// excessively-expensive computation requiring a time limit. We'll go ahead and apply a time
1329-
// limit just to be safe. Don't add it to the rollover bank, though.
1332+
// It's unclear to me if CompileUnboundScript() can get trapped in any
1333+
// infinite loops or excessively-expensive computation requiring a time
1334+
// limit. We'll go ahead and apply a time limit just to be safe. Don't
1335+
// add it to the rollover bank, though.
13301336
auto limitScope =
13311337
isolate->getLimitEnforcer().enterStartupJs(lock, limitErrorOrTime);
13321338
impl->unboundScriptOrMainModule =
13331339
jsg::NonModuleScript::compile(lock, script.mainScript, script.mainScriptName);
13341340
}
1335-
1336-
break;
13371341
}
13381342

13391343
KJ_CASE_ONEOF(modulesSource, ModulesSource) {
1344+
// This path is used for the new ESM worker syntax.
1345+
13401346
this->isPython = modulesSource.isPython;
13411347
if (!isolate->getApi().getFeatureFlags().getNewModuleRegistry()) {
13421348
kj::Own<void> limitScope;
@@ -1351,7 +1357,6 @@ Worker::Script::Script(kj::Own<const Isolate> isolateParam,
13511357
modulesSource.compileModules(lock, isolate->getApi());
13521358
}
13531359
impl->unboundScriptOrMainModule = kj::Path::parse(modulesSource.mainModule);
1354-
break;
13551360
}
13561361
}
13571362

@@ -1477,21 +1482,6 @@ void Worker::setupContext(
14771482
// =======================================================================================
14781483

14791484
namespace {
1480-
1481-
jsg::JsObject resolveNodeInspectModule(jsg::Lock& js) {
1482-
static constexpr auto kSpecifier = "node-internal:internal_inspect"_kj;
1483-
if (FeatureFlags::get(js).getNewModuleRegistry()) {
1484-
return KJ_ASSERT_NONNULL(
1485-
jsg::modules::ModuleRegistry::tryResolveModuleNamespace(js, kSpecifier));
1486-
}
1487-
1488-
// Use the original module registry implementation
1489-
auto registry = jsg::ModuleRegistry::from(js);
1490-
KJ_ASSERT(registry != nullptr);
1491-
auto inspectModule = registry->resolveInternalImport(js, kSpecifier);
1492-
return jsg::JsObject(inspectModule.getHandle(js).As<v8::Object>());
1493-
}
1494-
14951485
kj::Maybe<jsg::JsObject> tryResolveMainModule(jsg::Lock& js,
14961486
const kj::Path& mainModule,
14971487
jsg::JsContext<api::ServiceWorkerGlobalScope>& jsContext,
@@ -1867,7 +1857,8 @@ void Worker::handleLog(jsg::Lock& js,
18671857
// not even evaluate its arguments, so `message()` will not be called at all.
18681858
KJ_LOG(INFO, "console.log()", message());
18691859
} else {
1870-
// Write to stdio if allowed by console mode
1860+
// Write to stdio if allowed by console mode. This is making use of our internal
1861+
// built-in implementation of the node:util inspect API.
18711862
static const ColorMode COLOR_MODE = permitsColor();
18721863
#if _WIN32
18731864
static bool STDOUT_TTY = _isatty(_fileno(stdout));
@@ -1884,7 +1875,8 @@ void Worker::handleLog(jsg::Lock& js,
18841875
auto colors =
18851876
COLOR_MODE == ColorMode::ENABLED || (COLOR_MODE == ColorMode::ENABLED_IF_TTY && tty);
18861877

1887-
auto inspectModule = resolveNodeInspectModule(js);
1878+
static constexpr auto kSpecifier = "node-internal:internal_inspect"_kj;
1879+
auto inspectModule = KJ_ASSERT_NONNULL(js.resolveInternalModule(kSpecifier));
18881880
v8::Local<v8::Value> formatLogVal = inspectModule.get(js, "formatLog"_kj);
18891881
KJ_ASSERT(formatLogVal->IsFunction());
18901882
auto formatLog = formatLogVal.As<v8::Function>();

src/workerd/jsg/BUILD.bazel

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,9 @@ exports_files(["modules.capnp"])
88

99
wd_cc_library(
1010
name = "jsg",
11-
srcs = [
12-
"modules-new.c++",
13-
],
11+
srcs = [],
1412
hdrs = [
1513
"jsg-test.h",
16-
"modules-new.h",
1714
"type-wrapper.h",
1815
],
1916
local_defines = ["JSG_IMPLEMENTATION"],
@@ -86,6 +83,7 @@ wd_cc_library(
8683
"jsg.c++",
8784
"jsvalue.c++",
8885
"modules.c++",
86+
"modules-new.c++",
8987
"promise.c++",
9088
"resource.c++",
9189
"ser.c++",
@@ -103,6 +101,7 @@ wd_cc_library(
103101
"jsg.h",
104102
"jsvalue.h",
105103
"modules.h",
104+
"modules-new.h",
106105
"promise.h",
107106
"resource.h",
108107
"ser.h",
@@ -121,6 +120,7 @@ wd_cc_library(
121120
":meta",
122121
":modules_capnp",
123122
":observer",
123+
":url",
124124
"//src/workerd/util",
125125
"//src/workerd/util:sentry",
126126
"//src/workerd/util:thread-scopes",

src/workerd/jsg/jsg.c++

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
#include "setup.h"
88

9+
#include <workerd/jsg/modules-new.h>
10+
#include <workerd/jsg/modules.h>
911
#include <workerd/jsg/util.h>
1012
#include <workerd/util/thread-scopes.h>
1113

@@ -289,6 +291,37 @@ JsSymbol Lock::symbolAsyncDispose() {
289291
return IsolateBase::from(v8Isolate).getSymbolAsyncDispose();
290292
}
291293

294+
kj::Maybe<JsObject> Lock::resolveInternalModule(kj::StringPtr specifier) {
295+
auto& isolate = IsolateBase::from(v8Isolate);
296+
if (isolate.isUsingNewModuleRegistry()) {
297+
return jsg::modules::ModuleRegistry::tryResolveModuleNamespace(
298+
*this, specifier, jsg::modules::ResolveContext::Type::BUILTIN_ONLY);
299+
}
300+
301+
// Use the original module registry implementation
302+
auto registry = ModuleRegistry::from(*this);
303+
KJ_ASSERT(registry != nullptr);
304+
auto module = registry->resolveInternalImport(*this, specifier);
305+
return jsg::JsObject(module.getHandle(*this).As<v8::Object>());
306+
}
307+
308+
kj::Maybe<JsObject> Lock::resolveModule(kj::StringPtr specifier) {
309+
auto& isolate = IsolateBase::from(v8Isolate);
310+
if (isolate.isUsingNewModuleRegistry()) {
311+
return jsg::modules::ModuleRegistry::tryResolveModuleNamespace(
312+
*this, specifier, jsg::modules::ResolveContext::Type::BUNDLE);
313+
}
314+
315+
auto moduleRegistry = jsg::ModuleRegistry::from(*this);
316+
if (moduleRegistry == nullptr) return kj::none;
317+
auto spec = kj::Path::parse(specifier);
318+
auto& info = JSG_REQUIRE_NONNULL(
319+
moduleRegistry->resolve(*this, spec), Error, kj::str("No such module: ", specifier));
320+
auto module = info.module.getHandle(*this);
321+
jsg::instantiateModule(*this, module);
322+
return JsObject(module->GetModuleNamespace().As<v8::Object>());
323+
}
324+
292325
void ExternalMemoryAdjustment::maybeDeferAdjustment(
293326
v8::ExternalMemoryAccounter& externalMemoryAccounter, v8::Isolate* isolate, size_t amount) {
294327
if (isolate == nullptr) return;

src/workerd/jsg/jsg.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2718,6 +2718,14 @@ class Lock {
27182718
// Retrieve the worker environment.
27192719
virtual kj::Maybe<Value> getWorkerEnv() = 0;
27202720

2721+
// Resolve an internalk module namespace from the given specifier.
2722+
// This variation can be used only for internal built-ins.
2723+
kj::Maybe<JsObject> resolveInternalModule(kj::StringPtr specifier);
2724+
2725+
// Resolve a module namespace from the given specifier.
2726+
// This variation includes modules from the worker bundle.
2727+
kj::Maybe<JsObject> resolveModule(kj::StringPtr specifier);
2728+
27212729
private:
27222730
// Mark the jsg::Lock as being disallowed from being passed as a parameter into
27232731
// a kj promise coroutine. Note that this only blocks directly passing the Lock

0 commit comments

Comments
 (0)