[gjs/ewlsh/top-level-await] modules: Enable top-level await for modules
- From: Evan Welsh <ewlsh src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/ewlsh/top-level-await] modules: Enable top-level await for modules
- Date: Thu, 3 Feb 2022 06:14:20 +0000 (UTC)
commit 638a61d2df18562b2913e5c9b2b7d7bd08509c84
Author: Evan Welsh <contact evanwelsh com>
Date: Wed Feb 2 22:14:03 2022 -0800
modules: Enable top-level await for modules
gjs/context-private.h | 3 +
gjs/context.cpp | 119 +++++++++--
gjs/engine.cpp | 2 +-
gjs/internal.cpp | 11 +-
gjs/mainloop.cpp | 23 ++-
gjs/mainloop.h | 28 ++-
gjs/module.cpp | 19 +-
installed-tests/scripts/testCommandLineModules.sh | 18 +-
js.gresource.xml | 1 -
modules/internal/internalLoader.js | 229 ---------------------
modules/internal/loader.js | 230 +++++++++++++++++++++-
11 files changed, 402 insertions(+), 281 deletions(-)
---
diff --git a/gjs/context-private.h b/gjs/context-private.h
index 350f646a0..9ef774b21 100644
--- a/gjs/context-private.h
+++ b/gjs/context-private.h
@@ -122,6 +122,7 @@ class GjsContextPrivate : public JS::JobQueue {
bool m_draining_job_queue : 1;
bool m_should_profile : 1;
bool m_exec_as_module : 1;
+ bool m_unhandled_exception : 1;
bool m_should_listen_sigusr2 : 1;
// If profiling is enabled, we record the durations and reason for GC mark
@@ -182,6 +183,7 @@ class GjsContextPrivate : public JS::JobQueue {
}
void main_loop_hold() { m_main_loop.hold(); }
void main_loop_release() { m_main_loop.release(); }
+
[[nodiscard]] GjsProfiler* profiler() const { return m_profiler; }
[[nodiscard]] const GjsAtoms& atoms() const { return *m_atoms; }
[[nodiscard]] bool destroying() const { return m_destroying.load(); }
@@ -231,6 +233,7 @@ class GjsContextPrivate : public JS::JobQueue {
void schedule_gc(void) { schedule_gc_internal(true); }
void schedule_gc_if_needed(void);
+ void report_unhandled_exception() { m_unhandled_exception = true; }
void exit(uint8_t exit_code);
[[nodiscard]] bool should_exit(uint8_t* exit_code_p) const;
diff --git a/gjs/context.cpp b/gjs/context.cpp
index 3adc4d76e..2e570d229 100644
--- a/gjs/context.cpp
+++ b/gjs/context.cpp
@@ -523,6 +523,63 @@ gjs_context_constructed(GObject *object)
setup_dump_heap();
}
+static bool on_context_module_rejected_fallible(JSContext* cx, unsigned argc,
+ JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+ JS::HandleValue error = args.get(0);
+
+ GjsContextPrivate* gjs_cx = GjsContextPrivate::from_cx(cx);
+ gjs_cx->report_unhandled_exception();
+
+ gjs_log_exception_full(cx, error, nullptr, G_LOG_LEVEL_CRITICAL);
+
+ gjs_cx->main_loop_release();
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool on_context_module_rejected_infallible(JSContext* cx, unsigned,
+ JS::Value*) {
+ GjsContextPrivate::from_cx(cx)->main_loop_release();
+
+ g_error("Failed to load context module.");
+ return false;
+}
+
+static bool on_context_module_resolved(JSContext* cx, unsigned, JS::Value*) {
+ GjsContextPrivate::from_cx(cx)->main_loop_release();
+
+ return true;
+}
+
+static bool add_promise_reactions(JSContext* cx, JS::HandleValue promise,
+ JSNative resolve, JSNative reject,
+ const std::string& debug_tag) {
+ JS::RootedObject promiseObject(cx, promise.toObjectOrNull());
+ if (!promiseObject)
+ return false;
+
+ std::string resolved_tag = debug_tag + " async resolved";
+ std::string rejected_tag = debug_tag + " async rejected";
+
+ JS::RootedFunction onRejected(
+ cx,
+ js::NewFunctionWithReserved(cx, reject, 1, 0, resolved_tag.c_str()));
+ if (!onRejected)
+ return false;
+ JS::RootedFunction onResolved(
+ cx,
+ js::NewFunctionWithReserved(cx, resolve, 1, 0, rejected_tag.c_str()));
+ if (!onResolved)
+ return false;
+
+ JS::RootedObject resolved(cx, JS_GetFunctionObject(onResolved));
+ JS::RootedObject rejected(cx, JS_GetFunctionObject(onRejected));
+
+ return JS::AddPromiseReactions(cx, promiseObject, resolved, rejected);
+}
+
static void load_context_module(JSContext* cx, const char* uri,
const char* debug_identifier) {
JS::RootedObject loader(cx, gjs_module_load(cx, uri, uri));
@@ -537,11 +594,21 @@ static void load_context_module(JSContext* cx, const char* uri,
g_error("Failed to instantiate %s module.", debug_identifier);
}
- JS::RootedValue ignore(cx);
- if (!JS::ModuleEvaluate(cx, loader, &ignore)) {
+ JS::RootedValue evaluation_promise(cx);
+ if (!JS::ModuleEvaluate(cx, loader, &evaluation_promise)) {
gjs_log_exception(cx);
g_error("Failed to evaluate %s module.", debug_identifier);
}
+
+ GjsContextPrivate::from_cx(cx)->main_loop_hold();
+ bool ok = add_promise_reactions(
+ cx, evaluation_promise, on_context_module_resolved,
+ on_context_module_rejected_infallible, "context");
+
+ if (!ok) {
+ gjs_log_exception(cx);
+ g_error("Failed to load %s module.", debug_identifier);
+ }
}
GjsContextPrivate::GjsContextPrivate(JSContext* cx, GjsContext* public_context)
@@ -655,22 +722,22 @@ GjsContextPrivate::GjsContextPrivate(JSContext* cx, GjsContext* public_context)
g_error("Failed to define module global in internal global.");
}
- if (!gjs_load_internal_module(cx, "internalLoader")) {
+ if (!gjs_load_internal_module(cx, "loader")) {
gjs_log_exception(cx);
g_error("Failed to load internal module loaders.");
}
- load_context_module(cx,
- "resource:///org/gnome/gjs/modules/internal/loader.js",
- "module loader");
-
{
JSAutoRealm ar(cx, global);
+
load_context_module(
cx, "resource:///org/gnome/gjs/modules/esm/_bootstrap/default.js",
"ESM bootstrap");
}
+ // This should never happen unless bootstrap calls system.exit()
+ g_assert(m_main_loop.spin(this));
+
start_draining_job_queue();
}
@@ -1258,6 +1325,13 @@ bool GjsContextPrivate::handle_exit_code(bool no_sync_error_pending,
return false;
}
+ if (m_unhandled_exception) {
+ g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED,
+ "%s %s threw an exception", source_type, identifier);
+ *exit_code = 1;
+ return false;
+ }
+
// Assume success if no error was thrown and should exit isn't
// set
if (no_sync_error_pending) {
@@ -1289,13 +1363,15 @@ bool GjsContextPrivate::eval(const char* script, size_t script_len,
JS::RootedValue retval(m_cx);
bool ok = eval_with_scope(nullptr, script, script_len, filename, &retval);
- if (ok)
- m_main_loop.spin(this);
-
/* The promise job queue should be drained even on error, to finish
* outstanding async tasks before the context is torn down. Drain after
- * uncaught exceptions have been reported since draining runs callbacks. */
- {
+ * uncaught exceptions have been reported since draining runs callbacks.
+ *
+ * If the main loop returns false we cannot guarantee the state
+ * of our promise queue (a module promise could be pending) so
+ * instead of draining the queue we instead just exit.
+ */
+ if (!ok || m_main_loop.spin(this)) {
JS::AutoSaveExceptionState saved_exc(m_cx);
ok = run_jobs_fallible() && ok;
}
@@ -1349,17 +1425,26 @@ bool GjsContextPrivate::eval_module(const char* identifier,
return false;
}
- JS::RootedValue ignore(m_cx);
- bool ok = JS::ModuleEvaluate(m_cx, obj, &ignore);
+ JS::RootedValue evaluation_promise(m_cx);
+ bool ok = JS::ModuleEvaluate(m_cx, obj, &evaluation_promise);
+
+ if (ok) {
+ GjsContextPrivate::from_cx(m_cx)->main_loop_hold();
- if (ok)
- m_main_loop.spin(this);
+ ok = add_promise_reactions(
+ m_cx, evaluation_promise, on_context_module_resolved,
+ on_context_module_rejected_fallible, "context");
+ }
/* The promise job queue should be drained even on error, to finish
* outstanding async tasks before the context is torn down. Drain after
* uncaught exceptions have been reported since draining runs callbacks.
+ *
+ * If the main loop returns false we cannot guarantee the state
+ * of our promise queue (a module promise could be pending) so
+ * instead of draining the queue we instead just exit.
*/
- {
+ if (!ok || m_main_loop.spin(this)) {
JS::AutoSaveExceptionState saved_exc(m_cx);
ok = run_jobs_fallible() && ok;
}
diff --git a/gjs/engine.cpp b/gjs/engine.cpp
index 1da606b8d..9468947c3 100644
--- a/gjs/engine.cpp
+++ b/gjs/engine.cpp
@@ -169,7 +169,7 @@ JSContext* gjs_create_js_context(GjsContextPrivate* uninitialized_gjs) {
}
JS::ContextOptionsRef(cx)
.setAsmJS(enable_jit)
- .setTopLevelAwait(false)
+ .setTopLevelAwait(true)
.setPrivateClassFields(true)
.setPrivateClassMethods(true);
diff --git a/gjs/internal.cpp b/gjs/internal.cpp
index 57f3d97b6..68dd071d4 100644
--- a/gjs/internal.cpp
+++ b/gjs/internal.cpp
@@ -88,15 +88,8 @@ bool gjs_load_internal_module(JSContext* cx, const char* identifier) {
JS::RootedObject internal_global(cx, gjs_get_internal_global(cx));
JSAutoRealm ar(cx, internal_global);
- JS::RootedObject module(cx, JS::CompileModule(cx, options, buf));
- JS::RootedObject registry(cx, gjs_get_module_registry(internal_global));
-
- JS::RootedId key(cx, gjs_intern_string_to_id(cx, full_path));
-
- JS::RootedValue ignore(cx);
- if (!gjs_global_registry_set(cx, registry, key, module) ||
- !JS::ModuleInstantiate(cx, module) ||
- !JS::ModuleEvaluate(cx, module, &ignore)) {
+ JS::RootedValue ignored(cx);
+ if (!JS::Evaluate(cx, options, buf, &ignored)) {
return false;
}
diff --git a/gjs/mainloop.cpp b/gjs/mainloop.cpp
index 56f3db291..91109d61a 100644
--- a/gjs/mainloop.cpp
+++ b/gjs/mainloop.cpp
@@ -12,10 +12,17 @@
namespace Gjs {
-void MainLoop::spin(GjsContextPrivate* gjs) {
+bool MainLoop::spin(GjsContextPrivate* gjs) {
+ if (m_exiting)
+ return false;
+
// Check if System.exit() has been called.
- if (gjs->should_exit(nullptr))
- return;
+ if (gjs->should_exit(nullptr)) {
+ // Return false to indicate the loop is exiting due to an exit call,
+ // the queue is likely not empty
+ exit();
+ return false;
+ }
GjsAutoPointer<GMainContext, GMainContext, g_main_context_unref>
main_context(g_main_context_ref_thread_default());
@@ -26,12 +33,18 @@ void MainLoop::spin(GjsContextPrivate* gjs) {
// Only run the loop if there are pending jobs.
if (g_main_context_pending(main_context))
g_main_context_iteration(main_context, blocking);
- } while (
+
// If System.exit() has not been called
- !gjs->should_exit(nullptr) &&
+ if (gjs->should_exit(nullptr)) {
+ exit();
+ return false;
+ }
+ } while (
// and there are pending sources or the job queue is not empty
// continue spinning the event loop.
(can_block() || !gjs->empty()));
+
+ return true;
}
}; // namespace Gjs
diff --git a/gjs/mainloop.h b/gjs/mainloop.h
index 748dc7e49..f374060c8 100644
--- a/gjs/mainloop.h
+++ b/gjs/mainloop.h
@@ -18,8 +18,13 @@ class MainLoop {
// We nonetheless use grefcount here because it takes care of dealing with
// integer overflow for us.
grefcount m_hold_count;
+ bool m_exiting;
[[nodiscard]] bool can_block() {
+ // Don't block if exiting
+ if (m_exiting)
+ return false;
+
g_assert(!g_ref_count_compare(&m_hold_count, 0) &&
"main loop released too many times");
@@ -27,21 +32,38 @@ class MainLoop {
return !g_ref_count_compare(&m_hold_count, 1);
}
+ void exit() {
+ m_exiting = true;
+
+ // Reset the reference count to 1 to exit
+ g_ref_count_init(&m_hold_count);
+ }
+
public:
- MainLoop() { g_ref_count_init(&m_hold_count); }
+ MainLoop() : m_exiting(false) { g_ref_count_init(&m_hold_count); }
~MainLoop() {
g_assert(g_ref_count_compare(&m_hold_count, 1) &&
"mismatched hold/release on main loop");
}
- void hold() { g_ref_count_inc(&m_hold_count); }
+ void hold() {
+ // Don't allow new holds after exit() is called
+ if (m_exiting)
+ return;
+
+ g_ref_count_inc(&m_hold_count);
+ }
void release() {
+ // Ignore releases after exit(), exit() resets the refcount
+ if (m_exiting)
+ return;
+
bool zero [[maybe_unused]] = g_ref_count_dec(&m_hold_count);
g_assert(!zero && "main loop released too many times");
}
- void spin(GjsContextPrivate*);
+ [[nodiscard]] bool spin(GjsContextPrivate*);
};
}; // namespace Gjs
diff --git a/gjs/module.cpp b/gjs/module.cpp
index d1aee4941..0a30d4e15 100644
--- a/gjs/module.cpp
+++ b/gjs/module.cpp
@@ -540,7 +540,7 @@ JSObject* gjs_module_resolve(JSContext* cx, JS::HandleValue importingModulePriv,
// Can fail in JS::FinishDynamicModuleImport(), but will assert if anything
// fails in fetching the stashed values, since that would be a serious GJS bug.
GJS_JSAPI_RETURN_CONVENTION
-static bool finish_import(JSContext* cx, JS::DynamicImportStatus status,
+static bool finish_import(JSContext* cx, JS::HandleObject evaluation_promise,
const JS::CallArgs& args) {
GjsContextPrivate* priv = GjsContextPrivate::from_cx(cx);
priv->main_loop_release();
@@ -566,8 +566,9 @@ static bool finish_import(JSContext* cx, JS::DynamicImportStatus status,
args.rval().setUndefined();
- return JS::FinishDynamicModuleImport_NoTLA(
- cx, status, importing_module_priv, module_request, internal_promise);
+ return JS::FinishDynamicModuleImport(cx, evaluation_promise,
+ importing_module_priv, module_request,
+ internal_promise);
}
// Failing a JSAPI function may result either in an exception pending on the
@@ -577,7 +578,7 @@ static bool finish_import(JSContext* cx, JS::DynamicImportStatus status,
GJS_JSAPI_RETURN_CONVENTION
static bool fail_import(JSContext* cx, const JS::CallArgs& args) {
if (JS_IsExceptionPending(cx))
- return finish_import(cx, JS::DynamicImportStatus::Failed, args);
+ return finish_import(cx, nullptr, args);
return false;
}
@@ -592,7 +593,7 @@ static bool import_rejected(JSContext* cx, unsigned argc, JS::Value* vp) {
JS_SetPendingException(cx, args.get(0),
JS::ExceptionStackBehavior::DoNotCapture);
- return finish_import(cx, JS::DynamicImportStatus::Failed, args);
+ return finish_import(cx, nullptr, args);
}
GJS_JSAPI_RETURN_CONVENTION
@@ -607,12 +608,14 @@ static bool import_resolved(JSContext* cx, unsigned argc, JS::Value* vp) {
g_assert(args[0].isObject());
JS::RootedObject module(cx, &args[0].toObject());
- JS::RootedValue ignore(cx);
+ JS::RootedValue evaluation_promise(cx);
if (!JS::ModuleInstantiate(cx, module) ||
- !JS::ModuleEvaluate(cx, module, &ignore))
+ !JS::ModuleEvaluate(cx, module, &evaluation_promise))
return fail_import(cx, args);
- return finish_import(cx, JS::DynamicImportStatus::Ok, args);
+ JS::RootedObject evaluation_promise_object(cx,
+ &evaluation_promise.toObject());
+ return finish_import(cx, evaluation_promise_object, args);
}
bool gjs_dynamic_module_resolve(JSContext* cx,
diff --git a/installed-tests/scripts/testCommandLineModules.sh
b/installed-tests/scripts/testCommandLineModules.sh
index 6566dcb75..1965dcdc0 100755
--- a/installed-tests/scripts/testCommandLineModules.sh
+++ b/installed-tests/scripts/testCommandLineModules.sh
@@ -76,8 +76,24 @@ $gjs dynamicImplicitMainloop.js
test $? -eq 21
report "ensure dynamic imports resolve without an explicit mainloop"
+cat <<EOF >dynamicTopLevelAwaitImportee.js
+export const EXIT_CODE = 32;
+EOF
+
+cat <<EOF >dynamicTopLevelAwait.js
+const {EXIT_CODE} = await import("./dynamicTopLevelAwaitImportee.js")
+const system = await import('system');
+
+system.exit(EXIT_CODE);
+EOF
+
+$gjs -m dynamicTopLevelAwait.js
+test $? -eq 32
+report "ensure top level await can import modules"
+
rm -f doubledynamic.js doubledynamicImportee.js \
- dynamicImplicitMainloop.js dynamicImplicitMainloopImportee.js
+ dynamicImplicitMainloop.js dynamicImplicitMainloopImportee.js \
+ dynamicTopLevelAwait.js dynamicTopLevelAwaitImportee.js
echo "1..$total"
diff --git a/js.gresource.xml b/js.gresource.xml
index 4d3fde355..e12dea125 100644
--- a/js.gresource.xml
+++ b/js.gresource.xml
@@ -4,7 +4,6 @@
<gresources>
<gresource prefix="/org/gnome/gjs">
<!-- Internal modules -->
- <file>modules/internal/internalLoader.js</file>
<file>modules/internal/loader.js</file>
<!-- ESM-based modules -->
diff --git a/modules/internal/loader.js b/modules/internal/loader.js
index 3e661496a..2f3f71d8e 100644
--- a/modules/internal/loader.js
+++ b/modules/internal/loader.js
@@ -1,7 +1,223 @@
// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
// SPDX-FileCopyrightText: 2020 Evan Welsh <contact evanwelsh com>
-import {ImportError, InternalModuleLoader, ModulePrivate} from './internalLoader.js';
+/** @typedef {{ uri: string; scheme: string; host: string; path: string; query: Query }} Uri */
+
+/**
+ * Use '__internal: never' to prevent any object from being type compatible with Module
+ * because it is an internal type.
+ *
+ * @typedef {{__internal: never;}} Module
+ */
+/** @typedef {typeof moduleGlobalThis | typeof globalThis} Global */
+/** @typedef {{ load(uri: Uri): [contents: string, internal: boolean]; }} SchemeHandler */
+/** @typedef {{ [key: string]: string | undefined; }} Query */
+/** @typedef {(uri: string, contents: string) => Module} CompileFunc */
+
+/**
+ * Thrown when there is an error importing a module.
+ */
+class ImportError extends moduleGlobalThis.Error {
+ /**
+ * @param {string | undefined} message the import error message
+ */
+ constructor(message) {
+ super(message);
+
+ this.name = 'ImportError';
+ }
+}
+
+/**
+ * ModulePrivate is the "private" object of every module.
+ */
+class ModulePrivate {
+ /**
+ *
+ * @param {string} id the module's identifier
+ * @param {string} uri the module's URI
+ * @param {boolean} [internal] whether this module is "internal"
+ */
+ constructor(id, uri, internal = false) {
+ this.id = id;
+ this.uri = uri;
+ this.internal = internal;
+ }
+}
+
+/**
+ * Returns whether a string represents a relative path (e.g. ./, ../)
+ *
+ * @param {string} path a path to check if relative
+ * @returns {boolean}
+ */
+function isRelativePath(path) {
+ // Check if the path is relative. Note that this doesn't mean "relative
+ // path" in the GLib sense, as in "not absolute" — it means a relative path
+ // module specifier, which must start with a '.' or '..' path component.
+ return path.startsWith('./') || path.startsWith('../');
+}
+
+/**
+ * Handles resolving and loading URIs.
+ *
+ * @class
+ */
+class InternalModuleLoader {
+ /**
+ * @param {typeof globalThis} global the global object to handle module
+ * resolution
+ * @param {(string, string) => import("../types").Module} compileFunc the
+ * function to compile a source into a module for a particular global
+ * object. Should be compileInternalModule() for InternalModuleLoader,
+ * but overridden in ModuleLoader
+ */
+ constructor(global, compileFunc) {
+ this.global = global;
+ this.compileFunc = compileFunc;
+ }
+
+ /**
+ * Loads a file or resource URI synchronously
+ *
+ * @param {Uri} uri the file or resource URI to load
+ * @returns {[contents: string, internal?: boolean] | null}
+ */
+ loadURI(uri) {
+ if (uri.scheme === 'file' || uri.scheme === 'resource')
+ return [loadResourceOrFile(uri.uri)];
+
+ return null;
+ }
+
+ /**
+ * Resolves an import specifier given an optional parent importer.
+ *
+ * @param {string} specifier the import specifier
+ * @param {string | null} [parentURI] the URI of the module importing the specifier
+ * @returns {Uri | null}
+ */
+ resolveSpecifier(specifier, parentURI = null) {
+ try {
+ const uri = parseURI(specifier);
+
+ if (uri)
+ return uri;
+ } catch (err) {
+ // If it can't be parsed as a URI, try a relative path or return null.
+ }
+
+ if (isRelativePath(specifier)) {
+ if (!parentURI)
+ throw new ImportError('Cannot import relative path when module path is unknown.');
+
+ return this.resolveRelativePath(specifier, parentURI);
+ }
+
+ return null;
+ }
+
+ /**
+ * Resolves a path relative to a URI, throwing an ImportError if
+ * the parentURI isn't valid.
+ *
+ * @param {string} relativePath the relative path to resolve against the base URI
+ * @param {string} importingModuleURI the URI of the module triggering this
+ * resolve
+ * @returns {Uri}
+ */
+ resolveRelativePath(relativePath, importingModuleURI) {
+ // Ensure the parent URI is valid.
+ parseURI(importingModuleURI);
+
+ // Handle relative imports from URI-based modules.
+ const relativeURI = resolveRelativeResourceOrFile(importingModuleURI, relativePath);
+ if (!relativeURI)
+ throw new ImportError('File does not have a valid parent!');
+ return parseURI(relativeURI);
+ }
+
+ /**
+ * Compiles a module source text with the module's URI
+ *
+ * @param {ModulePrivate} priv a module private object
+ * @param {string} text the module source text to compile
+ * @returns {Module}
+ */
+ compileModule(priv, text) {
+ const compiled = this.compileFunc(priv.uri, text);
+
+ setModulePrivate(compiled, priv);
+
+ return compiled;
+ }
+
+ /**
+ * @param {string} specifier the specifier (e.g. relative path, root package) to resolve
+ * @param {string | null} importingModuleURI the URI of the module
+ * triggering this resolve
+ *
+ * @returns {Module | null}
+ */
+ resolveModule(specifier, importingModuleURI) {
+ const registry = getRegistry(this.global);
+
+ // Check if the module has already been loaded
+ let module = registry.get(specifier);
+ if (module)
+ return module;
+
+ // 1) Resolve path and URI-based imports.
+ const uri = this.resolveSpecifier(specifier, importingModuleURI);
+ if (uri) {
+ module = registry.get(uri.uri);
+
+ // Check if module is already loaded (relative handling)
+ if (module)
+ return module;
+
+ const result = this.loadURI(uri);
+ if (!result)
+ return null;
+
+ const [text, internal = false] = result;
+
+ const priv = new ModulePrivate(uri.uri, uri.uri, internal);
+ const compiled = this.compileModule(priv, text);
+
+ registry.set(uri.uri, compiled);
+ return compiled;
+ }
+
+ return null;
+ }
+
+ moduleResolveHook(importingModulePriv, specifier) {
+ const resolved = this.resolveModule(specifier, importingModulePriv.uri ?? null);
+ if (!resolved)
+ throw new ImportError(`Module not found: ${specifier}`);
+
+ return resolved;
+ }
+
+ moduleLoadHook(id, uri) {
+ const priv = new ModulePrivate(id, uri);
+
+ const result = this.loadURI(parseURI(uri));
+ // result can only be null if `this` is InternalModuleLoader. If `this`
+ // is ModuleLoader, then loadURI() will have thrown
+ if (!result)
+ throw new ImportError(`URI not found: ${uri}`);
+
+ const [text] = result;
+ const compiled = this.compileModule(priv, text);
+
+ const registry = getRegistry(this.global);
+ registry.set(id, compiled);
+
+ return compiled;
+ }
+}
class ModuleLoader extends InternalModuleLoader {
/**
@@ -27,7 +243,7 @@ class ModuleLoader extends InternalModuleLoader {
]);
/**
- * @type {Map<string, import("./internalLoader.js").SchemeHandler>}
+ * @type {Map<string, SchemeHandler>}
*
* A map of handlers for URI schemes (e.g. gi://)
*/
@@ -52,7 +268,7 @@ class ModuleLoader extends InternalModuleLoader {
/**
* @param {string} scheme the URI scheme to register
- * @param {import("./internalLoader.js").SchemeHandler} handler a handler
+ * @param {SchemeHandler} handler a handler
*/
registerScheme(scheme, handler) {
this.schemeHandlers.set(scheme, handler);
@@ -61,7 +277,7 @@ class ModuleLoader extends InternalModuleLoader {
/**
* Overrides InternalModuleLoader.loadURI
*
- * @param {import("./internalLoader.js").Uri} uri a Uri object to load
+ * @param {Uri} uri a Uri object to load
*/
loadURI(uri) {
if (uri.scheme) {
@@ -113,7 +329,7 @@ class ModuleLoader extends InternalModuleLoader {
* Resolves a module import with optional handling for relative imports.
* Overrides InternalModuleLoader.moduleResolveHook
*
- * @param {import("./internalLoader.js").ModulePrivate} importingModulePriv
+ * @param {ModulePrivate} importingModulePriv
* the private object of the module initiating the import
* @param {string} specifier the module specifier to resolve for an import
* @returns {import("./internalLoader").Module}
@@ -226,7 +442,7 @@ function generateGIModule(namespace, version) {
moduleLoader.registerScheme('gi', {
/**
- * @param {import("./internalLoader.js").Uri} uri the URI to load
+ * @param {Uri} uri the URI to load
*/
load(uri) {
const namespace = uri.host;
@@ -235,7 +451,7 @@ moduleLoader.registerScheme('gi', {
return [generateGIModule(namespace, version), true];
},
/**
- * @param {import("./internalLoader.js").Uri} uri the URI to load asynchronously
+ * @param {Uri} uri the URI to load asynchronously
*/
loadAsync(uri) {
// gi: only does string manipulation, so it is safe to use the same code for sync and async.
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]