[gjs/esm/dynamic-imports: 187/192] Implement dynamic imports
- From: Evan Welsh <ewlsh src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/esm/dynamic-imports: 187/192] Implement dynamic imports
- Date: Sat, 6 Feb 2021 05:52:50 +0000 (UTC)
commit 653ec55707fe709e642b455fa4af347c55172f64
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 | 3 ++
gjs/internal.cpp | 77 ++++++++++++++++++++++++++++++++++++++
gjs/internal.h | 5 +++
gjs/module.cpp | 27 +++++++++++++
gjs/module.h | 5 +++
modules/internal/internalLoader.js | 69 ++++++++++++++++++++++++++++++++++
modules/internal/loader.js | 59 +++++++++++++++++++++++++++++
8 files changed, 246 insertions(+)
---
diff --git a/gjs/context.cpp b/gjs/context.cpp
index ab3da86e..79a86ce2 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..10151e42 100644
--- a/gjs/global.cpp
+++ b/gjs/global.cpp
@@ -268,13 +268,16 @@ 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",
gjs_internal_resolve_relative_resource_or_file, 2, 0),
JS_FN("setGlobalModuleLoader", gjs_internal_set_global_module_loader, 2,
0),
+ JS_FN("setModuleDynamicImportHook", SetModuleDynamicImportHook, 2, 0),
JS_FN("setModulePrivate", gjs_internal_set_module_private, 2, 0),
JS_FN("uriExists", gjs_internal_uri_exists, 1, 0),
JS_FS_END};
diff --git a/gjs/internal.cpp b/gjs/internal.cpp
index 779e0864..e13c59f9 100644
--- a/gjs/internal.cpp
+++ b/gjs/internal.cpp
@@ -105,6 +105,59 @@ bool gjs_load_internal_module(JSContext* cx, const char* identifier) {
return true;
}
+bool SetModuleDynamicImportHook(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "setModuleDynamicImportHook", 2)) {
+ return false;
+ }
+
+ JS::RootedValue gv(cx, args[0]);
+ JS::RootedValue mv(cx, args[1]);
+
+ g_assert(gv.isObject());
+
+ // The hook is stored in the internal global.
+ JS::RootedObject global(cx, &gv.toObject());
+
+ // The dynamic hook is stored in the internal global.
+
+ gjs_set_global_slot(global, GjsGlobalSlot::DYNAMIC_IMPORT_HOOK, mv);
+
+ args.rval().setUndefined();
+ 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:
*
@@ -248,6 +301,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..bf742652 100644
--- a/gjs/internal.h
+++ b/gjs/internal.h
@@ -40,6 +40,11 @@ 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 SetModuleDynamicImportHook(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 0a9effab..bf5a41c6 100644
--- a/gjs/module.cpp
+++ b/gjs/module.cpp
@@ -474,3 +474,30 @@ 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_global_is_type(cx, GjsGlobalType::INTERNAL)) &&
+ "gjs_module_resolve can only be called from module-enabled "
+ "globals.");
+
+ GjsContextPrivate* gjs_cx = GjsContextPrivate::from_cx(cx);
+
+ JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
+
+ JSAutoRealm ar(cx, global);
+
+ JS::RootedValue hookValue(
+ cx, gjs_get_global_slot(global, GjsGlobalSlot::DYNAMIC_IMPORT_HOOK));
+
+ JS::RootedValueArray<3> args(cx);
+ args[0].set(aReferencingPrivate);
+ args[1].setString(aSpecifier);
+ args[2].setObject(*aPromise);
+
+ JS::RootedValue result(cx);
+ return JS_CallFunctionValue(cx, nullptr, hookValue, 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/internalLoader.js b/modules/internal/internalLoader.js
index 09acc16c..bd9933e6 100644
--- a/modules/internal/internalLoader.js
+++ b/modules/internal/internalLoader.js
@@ -84,6 +84,21 @@ export class InternalModuleLoader {
return null;
}
+
+ /**
+ * Loads a file or resource URI synchronously
+ *
+ * @param {Uri} uri the file or resource URI to load
+ * @returns {Promise<[string] | [string, boolean] | null>}
+ */
+ async loadURIAsync(uri) {
+ if (uri.scheme === 'file' || uri.scheme === 'resource') {
+ const result = await loadResourceOrFileAsync(uri.uri);
+ return [result];
+ }
+
+ return null;
+ }
/**
* Resolves an import specifier given an optional parent importer.
@@ -191,6 +206,60 @@ export class InternalModuleLoader {
return null;
}
+ /**
+ * @param {string} specifier the specifier (e.g. relative path, root package) to resolve
+ * @param {string | null} parentURI the URI of the module triggering this resolve
+ *
+ * @returns {Promise<import("../types").Module | null>}
+ */
+ async resolveModuleAsync(specifier, parentURI) {
+ 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, parentURI);
+
+ 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 esmodule = new ESModule(uri.uri, uri.uri, internal);
+
+ const compiled = this.compileModule(esmodule, text);
+
+ if (!compiled)
+ throw new ImportError(`Failed to register module: ${uri}`);
+
+
+ registry.set(uri.uri, compiled);
+ return compiled;
+ }
+
+ return null;
+ }
+
+
moduleResolveHook(importingModulePriv, specifier) {
const resolved = this.resolveModule(specifier, importingModulePriv.uri ?? null);
if (!resolved)
diff --git a/modules/internal/loader.js b/modules/internal/loader.js
index a44e8127..dad19b58 100644
--- a/modules/internal/loader.js
+++ b/modules/internal/loader.js
@@ -117,6 +117,53 @@ class ModuleLoader extends InternalModuleLoader {
return compiled;
}
+
+ /**
+ * Resolves a module import with optional handling for relative imports asynchronously.
+ *
+ * @param {string} specifier the module specifier to resolve for an import
+ * @param {string | null} moduleURI the importing module's URI or null if importing from the entry point
+ * @returns {Promise<import("../types").Module>}
+ */
+ async resolveModuleAsync(specifier, moduleURI) {
+ const module = await super.resolveModuleAsync(specifier, moduleURI);
+
+ if (module)
+ return module;
+
+
+ // 2) Resolve internal imports.
+
+ const uri = this.buildInternalURIs(specifier).find(u => {
+ let file = Gio.File.new_for_uri(u);
+
+ return file && file.query_exists(null);
+ });
+
+ if (!uri)
+ throw new ImportError(`Attempted to load unregistered global module: ${specifier}`);
+
+ const parsed = parseURI(uri);
+
+ if (parsed.scheme !== 'file' && parsed.scheme !== 'resource')
+ throw new ImportError('Only file:// and resource:// URIs are currently supported.');
+
+ const text = loadResourceOrFile(parsed.uri);
+
+ const priv = new ESModule(specifier, uri, true);
+
+ const compiled = this.compileModule(priv, text);
+
+ if (!compiled)
+ throw new ImportError(`Failed to register module: ${uri}`);
+
+ const registry = getRegistry(this.global);
+
+ if (!registry.has(specifier))
+ registry.set(specifier, compiled);
+
+ return compiled;
+ }
}
const moduleLoader = new ModuleLoader(moduleGlobalThis);
@@ -163,3 +210,15 @@ moduleLoader.registerScheme('gi', {
return [generateGIModule(namespace, version), true];
},
});
+
+setModuleDynamicImportHook(moduleGlobalThis, (module, specifier, promise) => {
+ moduleLoader.resolveModuleAsync(specifier, module.uri).then((m) => {
+ initAndEval(m);
+
+ finishDynamicModuleImport(module, specifier, promise);
+ }).catch(err => {
+ debug(err);
+ debug(err.stack);
+ throw new Error(`Dynamic module import failed: ${err}`);
+ });
+});
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]