[gjs/esm/dynamic-imports: 8/10] Implement dynamic imports
- From: Philip Chimento <pchimento src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/esm/dynamic-imports: 8/10] Implement dynamic imports
- Date: Sun, 7 Feb 2021 22:14:19 +0000 (UTC)
commit ac03c2e79ce09f7070d67bdf7ff8f35a40376472
Author: Evan Welsh <contact evanwelsh com>
Date: Sat Nov 14 14:36:27 2020 -0600
Implement dynamic imports
gjs/context.cpp | 1 +
gjs/module.cpp | 125 +++++++++++++++++++++++++++++++++++++++++++++
gjs/module.h | 6 +++
modules/internal/loader.js | 79 ++++++++++++++++++++++++++++
4 files changed, 211 insertions(+)
---
diff --git a/gjs/context.cpp b/gjs/context.cpp
index 4d8e7144..64ef9605 100644
--- a/gjs/context.cpp
+++ b/gjs/context.cpp
@@ -553,6 +553,7 @@ GjsContextPrivate::GjsContextPrivate(JSContext* cx, GjsContext* public_context)
}
JS::SetModuleResolveHook(rt, gjs_module_resolve);
+ JS::SetModuleDynamicImportHook(rt, gjs_dynamic_module_resolve);
JS::SetModuleMetadataHook(rt, gjs_populate_module_meta);
if (!JS_DefineProperty(m_cx, internal_global, "moduleGlobalThis", global,
diff --git a/gjs/module.cpp b/gjs/module.cpp
index c810f7c9..25644753 100644
--- a/gjs/module.cpp
+++ b/gjs/module.cpp
@@ -21,6 +21,8 @@
#include <js/Conversions.h>
#include <js/GCVector.h> // for RootedVector
#include <js/Id.h>
+#include <js/Modules.h>
+#include <js/Promise.h>
#include <js/PropertyDescriptor.h>
#include <js/RootingAPI.h>
#include <js/SourceText.h>
@@ -29,6 +31,7 @@
#include <js/Value.h>
#include <js/ValueArray.h>
#include <jsapi.h> // for JS_DefinePropertyById, ...
+#include <jsfriendapi.h> // for SetFunctionNativeReserved
#include "gjs/atoms.h"
#include "gjs/context-private.h"
@@ -484,3 +487,125 @@ JSObject* gjs_module_resolve(JSContext* cx, JS::HandleValue importingModulePriv,
g_assert(result.isObject() && "resolve hook failed to return an object!");
return &result.toObject();
}
+
+inline bool gjs_dynamic_module_get_meta(
+ JSContext* cx, JS::HandleObject meta,
+ JS::MutableHandleValue importing_module_priv,
+ JS::MutableHandleString specifier,
+ JS::MutableHandleObject internal_promise) {
+ JS::RootedValue v_specifier(cx);
+ JS::RootedValue v_internal_promise(cx);
+ if (!JS_GetProperty(cx, meta, "priv", importing_module_priv) ||
+ !JS_GetProperty(cx, meta, "promise", &v_internal_promise) ||
+ !JS_GetProperty(cx, meta, "specifier", &v_specifier))
+ return false;
+
+ g_assert(v_specifier.isString());
+ g_assert(v_internal_promise.isObject());
+
+ specifier.set(v_specifier.toString());
+ internal_promise.set(&v_internal_promise.toObject());
+
+ return true;
+}
+
+bool gjs_dynamic_module_rejected(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+ JS::Value priv_value = js::GetFunctionNativeReserved(&args.callee(), 0);
+ g_assert(!priv_value.isNull() && "Dynamic module rejection called twice");
+ JS::RootedObject meta(cx, &priv_value.toObject());
+ g_assert(meta && "Dynamic module rejection called twice");
+ js::SetFunctionNativeReserved(&args.callee(), 0, JS::NullValue());
+
+ JS::RootedValue importing_module_priv(cx);
+ JS::RootedString specifier(cx);
+ JS::RootedObject internal_promise(cx);
+ if (!gjs_dynamic_module_get_meta(cx, meta, &importing_module_priv,
+ &specifier, &internal_promise))
+ return false;
+
+ return JS::FinishDynamicModuleImport(cx, importing_module_priv, specifier,
+ internal_promise);
+}
+
+bool gjs_dynamic_module_resolved(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+
+ JS::Value priv_value = js::GetFunctionNativeReserved(&args.callee(), 0);
+ g_assert(!priv_value.isNull() && "Dynamic module resolution called twice");
+ JS::RootedObject meta(cx, &priv_value.toObject());
+ g_assert(meta && "Dynamic module resolution called twice");
+ js::SetFunctionNativeReserved(&args.callee(), 0, JS::NullValue());
+
+ JS::RootedValue importing_module_priv(cx);
+ JS::RootedString specifier(cx);
+ JS::RootedObject internal_promise(cx);
+ if (!gjs_dynamic_module_get_meta(cx, meta, &importing_module_priv,
+ &specifier, &internal_promise))
+ return false;
+
+ JS::RootedObject global(cx, gjs_get_import_global(cx));
+ JSAutoRealm ar(cx, global);
+
+ g_assert(args[0].isObject());
+ JS::RootedObject module(cx, &args[0].toObject());
+
+ if (!JS::ModuleInstantiate(cx, module) || !JS::ModuleEvaluate(cx, module))
+ return JS::FinishDynamicModuleImport(cx, importing_module_priv,
+ specifier, internal_promise);
+
+ return JS::FinishDynamicModuleImport(cx, importing_module_priv, specifier,
+ internal_promise);
+}
+
+bool gjs_dynamic_module_resolve(JSContext* cx,
+ JS::HandleValue importing_module_priv,
+ JS::HandleString specifier,
+ JS::HandleObject internal_promise) {
+ g_assert(gjs_global_is_type(cx, GjsGlobalType::DEFAULT) &&
+ "gjs_dynamic_module_resolve can only be called from the default "
+ "global.");
+
+ JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+ JSAutoRealm ar(cx, global);
+
+ JS::RootedValue v_loader(
+ cx, gjs_get_global_slot(global, GjsGlobalSlot::MODULE_LOADER));
+ g_assert(v_loader.isObject());
+ JS::RootedObject loader(cx, &v_loader.toObject());
+
+ JS::RootedObject meta(cx, JS_NewPlainObject(cx));
+ if (!JS_DefineProperty(cx, meta, "specifier", specifier,
+ JSPROP_PERMANENT) ||
+ !JS_DefineProperty(cx, meta, "promise", internal_promise,
+ JSPROP_PERMANENT) ||
+ !JS_DefineProperty(cx, meta, "priv", importing_module_priv,
+ JSPROP_PERMANENT))
+ return false;
+
+ JS::RootedValueArray<2> args(cx);
+ args[0].set(importing_module_priv);
+ args[1].setString(specifier);
+
+ JS::RootedValue result(cx);
+ if (!JS::Call(cx, loader, "moduleResolveAsyncHook", args, &result))
+ return JS::FinishDynamicModuleImport(cx, importing_module_priv,
+ specifier, internal_promise);
+
+ JS::RootedObject resolved(
+ cx, JS_GetFunctionObject(js::NewFunctionWithReserved(
+ cx, gjs_dynamic_module_resolved, 2, 0, "import resolved")));
+ JS::RootedObject rejected(
+ cx, JS_GetFunctionObject(js::NewFunctionWithReserved(
+ cx, gjs_dynamic_module_rejected, 2, 0, "import rejected")));
+ js::SetFunctionNativeReserved(resolved, 0, JS::ObjectValue(*meta));
+ js::SetFunctionNativeReserved(rejected, 0, JS::ObjectValue(*meta));
+
+ JS::RootedObject promise(cx, &result.toObject());
+
+ if (!JS::AddPromiseReactions(cx, promise, resolved, rejected))
+ return false;
+
+ return true;
+}
diff --git a/gjs/module.h b/gjs/module.h
index c754b114..dfe6f18c 100644
--- a/gjs/module.h
+++ b/gjs/module.h
@@ -39,4 +39,10 @@ GJS_JSAPI_RETURN_CONVENTION
bool gjs_populate_module_meta(JSContext* cx, JS::HandleValue private_ref,
JS::HandleObject meta_object);
+GJS_JSAPI_RETURN_CONVENTION
+bool gjs_dynamic_module_resolve(JSContext* cx,
+ JS::HandleValue importing_module_priv,
+ JS::HandleString specifier,
+ JS::HandleObject internal_promise);
+
#endif // GJS_MODULE_H_
diff --git a/modules/internal/loader.js b/modules/internal/loader.js
index fde96eb0..59e01de9 100644
--- a/modules/internal/loader.js
+++ b/modules/internal/loader.js
@@ -122,6 +122,78 @@ class ModuleLoader extends InternalModuleLoader {
return this.resolveBareSpecifier(specifier);
}
+
+ moduleResolveAsyncHook(importingModulePriv, specifier) {
+ if (!importingModulePriv || !importingModulePriv.uri)
+ throw new ImportError('Cannot resolve relative imports from an unknown file.');
+
+ return this.resolveModuleAsync(specifier, importingModulePriv.uri);
+ }
+
+ /**
+ * Resolves a module import with optional handling for relative imports asynchronously.
+ *
+ * @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 {import("../types").Module}
+ */
+ async resolveModuleAsync(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 = await this.loadURIAsync(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;
+ }
+
+ // 2) Resolve internal imports.
+
+ return this.resolveBareSpecifier(specifier);
+ }
+
+ /**
+ * Loads a file or resource URI asynchronously
+ *
+ * @param {Uri} uri the file or resource URI to load
+ * @returns {Promise<[string] | [string, boolean] | null>}
+ */
+ async loadURIAsync(uri) {
+ if (uri.scheme) {
+ const loader = this.schemeHandlers.get(uri.scheme);
+
+ if (loader)
+ return loader.loadAsync(uri);
+ }
+
+ if (uri.scheme === 'file' || uri.scheme === 'resource') {
+ const result = await loadResourceOrFileAsync(uri.uri);
+ return [result];
+ }
+
+ return null;
+ }
}
const moduleLoader = new ModuleLoader(moduleGlobalThis);
@@ -167,4 +239,11 @@ moduleLoader.registerScheme('gi', {
return [generateGIModule(namespace, version), true];
},
+ /**
+ * @param {import("./internalLoader.js").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.
+ return this.load(uri);
+ },
});
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]