[gjs/esm/dynamic-imports: 9/13] Implement dynamic imports
- From: Evan Welsh <ewlsh src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/esm/dynamic-imports: 9/13] Implement dynamic imports
- Date: Sun, 7 Feb 2021 04:18:58 +0000 (UTC)
commit eeaf3790de37d3a1bce163051799a86d270a24d1
Author: Evan Welsh <noreply evanwelsh com>
Date: Sat Nov 14 14:36:27 2020 -0600
Implement dynamic imports
gjs/context.cpp | 1 +
gjs/global.cpp | 2 ++
gjs/internal.cpp | 55 +++++++++++++++++++++++++++++++
gjs/internal.h | 3 ++
gjs/module.cpp | 25 ++++++++++++++
gjs/module.h | 5 +++
modules/internal/loader.js | 82 ++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 173 insertions(+)
---
diff --git a/gjs/context.cpp b/gjs/context.cpp
index c3810d97..1a85f6c6 100644
--- a/gjs/context.cpp
+++ b/gjs/context.cpp
@@ -549,6 +549,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/global.cpp b/gjs/global.cpp
index 6e0dac3b..1dcb613a 100644
--- a/gjs/global.cpp
+++ b/gjs/global.cpp
@@ -268,7 +268,9 @@ class GjsInternalGlobal : GjsBaseGlobal {
JS_FN("compileModule", gjs_internal_compile_module, 2, 0),
JS_FN("compileInternalModule", gjs_internal_compile_internal_module, 2,
0),
+ JS_FN("finishDynamicModuleImport", FinishDynamicModuleImport, 3, 0),
JS_FN("getRegistry", gjs_internal_get_registry, 1, 0),
+ JS_FN("initAndEval", InitAndEval, 1, 0),
JS_FN("loadResourceOrFile", gjs_internal_load_resource_or_file, 1, 0),
JS_FN("parseURI", gjs_internal_parse_uri, 1, 0),
JS_FN("resolveRelativeResourceOrFile",
diff --git a/gjs/internal.cpp b/gjs/internal.cpp
index 184f2457..ef620d33 100644
--- a/gjs/internal.cpp
+++ b/gjs/internal.cpp
@@ -108,6 +108,37 @@ bool gjs_load_internal_module(JSContext* cx, const char* identifier) {
return true;
}
+bool FinishDynamicModuleImport(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "finishDynamicModuleImport", 3)) {
+ return false;
+ }
+
+ if (JS_IsExceptionPending(cx)) {
+ gjs_log_exception_uncaught(cx);
+ gjs_throw(cx, "Uncaught exception in module!");
+ return false;
+ }
+
+ JS::RootedString specifier(cx, args[1].toString());
+ JS::RootedObject promise(cx, &args[2].toObject());
+
+ auto priv = GjsContextPrivate::from_cx(cx);
+ // gjs_module_resolve is called within whatever realm the dynamic import is
+ // finished in.
+ {
+ JSAutoRealm ar(cx, priv->global());
+
+ if (!JS_WrapObject(cx, &promise)) {
+ gjs_throw(cx, "Failed to wrap dynamic imports' promise.");
+ return false;
+ }
+
+ return JS::FinishDynamicModuleImport(cx, args[0], specifier, promise);
+ }
+}
+
/**
* gjs_internal_set_global_module_loader:
*
@@ -251,6 +282,30 @@ bool gjs_internal_set_module_private(JSContext* cx, unsigned argc,
return true;
}
+bool InitAndEval(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!args.requireAtLeast(cx, "initAndEval", 1)) {
+ return false;
+ }
+ JS::RootedObject global(cx, gjs_get_import_global(cx));
+ JSAutoRealm ar(cx, global);
+
+ JS::RootedObject new_module(cx, &args[0].toObject());
+
+ if (!JS::ModuleInstantiate(cx, new_module)) {
+ gjs_log_exception(cx);
+ return false;
+ }
+ if (!JS::ModuleEvaluate(cx, new_module)) {
+ gjs_log_exception(cx);
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
/**
* gjs_internal_get_registry:
*
diff --git a/gjs/internal.h b/gjs/internal.h
index 212e5458..6b9d525a 100644
--- a/gjs/internal.h
+++ b/gjs/internal.h
@@ -40,6 +40,9 @@ bool gjs_internal_resolve_relative_resource_or_file(JSContext* cx,
unsigned argc,
JS::Value* vp);
+bool InitAndEval(JSContext* cx, unsigned argc, JS::Value* vp);
+bool FinishDynamicModuleImport(JSContext* cx, unsigned argc, JS::Value* vp);
+
GJS_JSAPI_RETURN_CONVENTION
bool gjs_internal_load_resource_or_file(JSContext* cx, unsigned argc,
JS::Value* vp);
diff --git a/gjs/module.cpp b/gjs/module.cpp
index c810f7c9..2d4a7e06 100644
--- a/gjs/module.cpp
+++ b/gjs/module.cpp
@@ -484,3 +484,28 @@ JSObject* gjs_module_resolve(JSContext* cx, JS::HandleValue importingModulePriv,
g_assert(result.isObject() && "resolve hook failed to return an object!");
return &result.toObject();
}
+
+bool gjs_dynamic_module_resolve(JSContext* cx,
+ JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSString*> aSpecifier,
+ JS::Handle<JSObject*> aPromise) {
+ 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::RootedValueArray<3> args(cx);
+ args[0].set(aReferencingPrivate);
+ args[1].setString(aSpecifier);
+ args[2].setObject(*aPromise);
+
+ JS::RootedValue result(cx);
+ return JS::Call(cx, loader, "moduleDynamicImportHook", args, &result);
+}
diff --git a/gjs/module.h b/gjs/module.h
index c754b114..d303530f 100644
--- a/gjs/module.h
+++ b/gjs/module.h
@@ -39,4 +39,9 @@ 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::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSString*> aSpecifier,
+ JS::Handle<JSObject*> aPromise);
#endif // GJS_MODULE_H_
diff --git a/modules/internal/loader.js b/modules/internal/loader.js
index fde96eb0..464a1034 100644
--- a/modules/internal/loader.js
+++ b/modules/internal/loader.js
@@ -76,6 +76,16 @@ class ModuleLoader extends InternalModuleLoader {
throw new ImportError(`Invalid module URI: ${uri.uri}`);
}
+ moduleDynamicImportHook(module, specifier, promise) {
+ this.resolveModuleAsync(specifier, module.uri).then((m) => {
+ initAndEval(m);
+
+ finishDynamicModuleImport(module, specifier, promise);
+ }).catch(err => {
+ throw new ImportError(err.message);
+ });
+ }
+
/**
* Resolves a bare specifier like 'system' against internal resources,
* erroring if no resource is found.
@@ -122,6 +132,71 @@ class ModuleLoader extends InternalModuleLoader {
return this.resolveBareSpecifier(specifier);
}
+
+ /**
+ * Resolves a module import with optional handling for relative imports asynchronously.
+ *
+ * @param {import("./internalLoader.js").ModulePrivate} importingModulePriv
+ * the private object of the module initiating the import
+ * @param {string} specifier the module specifier to resolve for an import
+ * @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 +242,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]