[gjs/esm/dynamic-imports] esm: Enable dynamic imports.
- From: Evan Welsh <ewlsh src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/esm/dynamic-imports] esm: Enable dynamic imports.
- Date: Sun, 28 Jun 2020 18:26:09 +0000 (UTC)
commit b6d8ada44b1aa4b147eea9cf5c94d4fe11f564a0
Author: Evan Welsh <noreply evanwelsh com>
Date: Sun Apr 12 18:50:32 2020 -0500
esm: Enable dynamic imports.
gjs/global.cpp | 3 +
gjs/internal.cpp | 41 ++++++++++++
gjs/internal.h | 6 ++
gjs/internal/module.js | 127 +++++++++++++++++++++++++++++++-----
gjs/internal/module/loaders/file.js | 29 +++++++-
gjs/module.cpp | 25 +++++++
gjs/module.h | 5 ++
7 files changed, 218 insertions(+), 18 deletions(-)
---
diff --git a/gjs/global.cpp b/gjs/global.cpp
index 2604dd84..314619a2 100644
--- a/gjs/global.cpp
+++ b/gjs/global.cpp
@@ -320,6 +320,7 @@ class GjsGlobal {
JSRuntime* rt = JS_GetRuntime(cx);
JS::SetModuleResolveHook(rt, gjs_module_resolve);
+ JS::SetModuleDynamicImportHook(rt, gjs_dynamic_module_resolve);
JS::SetModuleMetadataHook(rt, gjs_populate_module_meta);
}
@@ -443,10 +444,12 @@ class GjsInternalGlobal {
JS_FN("getModuleURI", GetModuleURI, 1, 0),
JS_FN("compileAndEvalModule", CompileAndEvalModule, 1, 0),
JS_FN("debug", Debug, 1, 0),
+ JS_FN("finishDynamicModuleImport", FinishDynamicModuleImport, 3, 0),
JS_FN("lookupInternalModule", LookupInternalModule, 1, 0),
JS_FN("lookupModule", LookupModule, 1, 0),
JS_FN("registerModule", RegisterModule, 5, 0),
JS_FN("registerInternalModule", RegisterInternalModule, 5, 0),
+ JS_FN("setModuleDynamicImportHook", SetModuleDynamicImportHook, 1, 0),
JS_FN("setModuleResolveHook", SetModuleResolveHook, 1, 0),
JS_FS_END};
diff --git a/gjs/internal.cpp b/gjs/internal.cpp
index 6863978c..e6dcbd56 100644
--- a/gjs/internal.cpp
+++ b/gjs/internal.cpp
@@ -169,6 +169,47 @@ bool SetModuleResolveHook(JSContext* cx, unsigned argc, JS::Value* vp) {
return true;
}
+bool SetModuleDynamicImportHook(JSContext* cx, unsigned argc, JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.requireAtLeast(cx, "setModuleDynamicImportHook", 1)) {
+ return false;
+ }
+
+ JS::RootedValue mv(cx, args[0]);
+ GjsContextPrivate* priv = GjsContextPrivate::from_cx(cx);
+ // The dynamic hook is stored in the internal global.
+ JS::RootedObject global(cx, priv->internal_global());
+ gjs_set_global_slot(global, GjsInternalGlobalSlot::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;
+ }
+
+ 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);
+ }
+}
+
bool CompileAndEvalModule(JSContext* cx, unsigned argc, JS::Value* vp) {
JS::CallArgs args = CallArgsFromVp(argc, vp);
diff --git a/gjs/internal.h b/gjs/internal.h
index 475b6ce2..148f3f70 100644
--- a/gjs/internal.h
+++ b/gjs/internal.h
@@ -40,9 +40,15 @@ bool gjs_load_internal_script(JSContext* cx, const char* identifier);
// setModuleResolveHook
bool SetModuleResolveHook(JSContext* cx, unsigned argc, JS::Value* vp);
+// setModuleDynamicImportHook()
+bool SetModuleDynamicImportHook(JSContext* cx, unsigned argc, JS::Value* vp);
+
// compileAndEvalModule(id: string)
bool CompileAndEvalModule(JSContext* cx, unsigned argc, JS::Value* vp);
+// finishDynamicModuleImport()
+bool FinishDynamicModuleImport(JSContext* cx, unsigned argc, JS::Value* vp);
+
// registerModule(id: string, path: string, text: string, length: number, ?:
// boolean)
bool RegisterModule(JSContext* cx, unsigned argc, JS::Value* vp);
diff --git a/gjs/internal/module.js b/gjs/internal/module.js
index 6681e8a7..95f7cf7d 100644
--- a/gjs/internal/module.js
+++ b/gjs/internal/module.js
@@ -36,6 +36,7 @@ const allowedRelatives = ['file', 'resource'];
const relativeResolvers = new Map();
const loaders = new Map();
+const asyncLoaders = new Map();
function registerScheme(...schemes) {
function forEach(fn, ...args) {
@@ -56,6 +57,13 @@ function registerScheme(...schemes) {
loaders.set(scheme, handler);
});
+ return schemeBuilder;
+ },
+ asyncLoader(handler) {
+ forEach(scheme => {
+ asyncLoaders.set(scheme, handler);
+ });
+
return schemeBuilder;
},
};
@@ -153,33 +161,38 @@ function loadURI(uri) {
}
}
+async function loadURIAsync(uri) {
+ debug(`URI: ${uri.raw}`);
+
+ if (uri.scheme) {
+ const loader = asyncLoaders.get(uri.scheme);
+
+ if (loader)
+ return await loader(uri);
+ else
+ throw new ImportError(`No resolver found for URI: ${uri.raw || uri}`);
+ } else {
+ throw new ImportError(`Unable to load module, module has invalid URI: ${uri.raw || uri}`);
+ }
+}
+
function resolveSpecifier(specifier, moduleURI = null) {
// If a module has a path, we'll have stored it in the host field
- let output = null;
- let uri = null;
let parsedURI = null;
if (isRelativePath(specifier)) {
let resolved = resolveRelativePath(moduleURI, specifier);
parsedURI = parseURI(resolved);
- uri = resolved;
} else {
const parsed = parseURI(specifier);
if (parsed) {
- uri = parsed.raw;
parsedURI = parsed;
}
}
- if (parsedURI)
- output = loadURI(parsedURI);
-
- if (!output)
- return null;
-
- return {output, uri};
+ return parsedURI;
}
function resolveModule(specifier, moduleURI) {
@@ -203,10 +216,10 @@ function resolveModule(specifier, moduleURI) {
// 1) Resolve path and URI-based imports.
- const resolved = resolveSpecifier(specifier, moduleURI);
+ const parsedURI = resolveSpecifier(specifier, moduleURI);
- if (resolved) {
- const {output, uri} = resolved;
+ if (parsedURI) {
+ const uri = parsedURI.raw;
debug(`Full path found: ${uri}`);
@@ -216,7 +229,10 @@ function resolveModule(specifier, moduleURI) {
if (lookup_module)
return lookup_module;
- const text = output;
+ let text = loadURI(parsedURI);
+
+ if (!text)
+ return null;
if (!registerModule(uri, uri, text, text.length, false))
throw new ImportError(`Failed to register module: ${uri}`);
@@ -243,6 +259,87 @@ function resolveModule(specifier, moduleURI) {
return lookupInternalModule(specifier);
}
+async function resolveModuleAsync(specifier, moduleURI) {
+ debug(`Resolving (asynchronously): ${specifier}...`);
+
+ // Check if the module has already been loaded (absolute imports)
+ if (lookupModule(specifier)) {
+ // Resolve if found.
+ return;
+ }
+
+ if (lookupInternalModule(specifier)) {
+ return;
+ }
+
+ const parsedURI = resolveSpecifier(specifier, moduleURI);
+
+ if (parsedURI) {
+ const uri = parsedURI.raw;
+
+ if (lookupModule(uri)) {
+ return;
+ }
+
+ let text = await loadURIAsync(parsedURI);
+
+ if (!text)
+ return;
+
+ if (!registerModule(uri, uri, text, text.length)) {
+ throw new ImportError(`Failed to register module: ${uri}`);
+ }
+
+ const registered = lookupModule(uri);
+
+ if (registered) {
+ if (compileAndEvalModule(uri)) {
+ return;
+ } else {
+ throw new ImportError(`Failed to compile and evaluate module ${uri}.`);
+ }
+ }
+
+ // Fail by default.
+ throw new ImportError('Unknown dynamic import error occured.');
+ } else {
+ for (const uri of buildInternalPaths(specifier)) {
+ try {
+ const text = await loadURIAsync(uri);
+
+ if (!registerInternalModule(specifier, uri, text, text.length)) {
+ throw new ImportError(`Failed to register internal module: ${specifier} at ${uri}.`);
+ }
+
+ if (lookupInternalModule(specifier)) {
+ return;
+ }
+ } catch (err) {
+ debug(`Failed to load ${uri}.`);
+ }
+ }
+
+ throw new ImportError(`Attempted to load unregistered global module: ${specifier}`);
+ }
+}
+
+setModuleDynamicImportHook((referencingInfo, specifier, promise) => {
+ debug('Starting dynamic import...');
+ const uri = getModuleUri(referencingInfo);
+
+ if (uri)
+ debug(`Found base URI: ${uri}`);
+
+ resolveModuleAsync(specifier, uri).then(() => {
+ debug('Successfully imported module!');
+ finishDynamicModuleImport(referencingInfo, specifier, promise);
+ }).catch(err => {
+ debug(err);
+ debug(err.stack);
+ throw new Error(`Dynamic module import failed: ${err}`);
+ });
+});
+
setModuleResolveHook((referencingInfo, specifier) => {
debug('Starting module import...');
const uri = getModuleURI(referencingInfo);
diff --git a/gjs/internal/module/loaders/file.js b/gjs/internal/module/loaders/file.js
index e36b1135..1e12db67 100644
--- a/gjs/internal/module/loaders/file.js
+++ b/gjs/internal/module/loaders/file.js
@@ -26,15 +26,34 @@ function fromBytes(bytes) {
return ByteUtils.toString(bytes, 'utf-8');
}
-function loadFileSync(output, full_path) {
+function loadFileSync(output) {
try {
const [, bytes] = output.load_contents(null);
return fromBytes(bytes);
} catch (error) {
- throw new Error(`Unable to load file from: ${full_path}`);
+ throw new Error(`Unable to load file from: ${output && output.get_uri()}`);
}
}
+function loadFileAsync(output) {
+ return new Promise((resolve, reject) => {
+ output.load_contents_async(
+ null,
+ (file, res) => {
+ debug('Contents loaded!');
+
+ try {
+ const [, bytes] = file.load_contents_finish(res);
+ const text = fromBytes(bytes);
+
+ resolve(text);
+ } catch (error) {
+ reject(error);
+ }
+ });
+ });
+}
+
registerScheme('file', 'resource')
.relativeResolver((moduleURI, relativePath) => {
let module_file = Gio.File.new_for_uri(moduleURI.raw);
@@ -43,8 +62,12 @@ registerScheme('file', 'resource')
let output = module_parent_file.resolve_relative_path(relativePath);
return output.get_uri();
+ }).asyncLoader(uri => {
+ const file = Gio.File.new_for_uri(uri.raw);
+
+ return loadFileAsync(file);
}).loader(uri => {
const file = Gio.File.new_for_uri(uri.raw);
- return loadFileSync(file, file.get_uri());
+ return loadFileSync(file);
});
diff --git a/gjs/module.cpp b/gjs/module.cpp
index 79dbb59e..3899c272 100644
--- a/gjs/module.cpp
+++ b/gjs/module.cpp
@@ -401,6 +401,31 @@ JSObject* gjs_module_resolve(JSContext* cx, JS::HandleValue importer,
return module;
}
+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 on the default "
+ "global.");
+
+ GjsContextPrivate* gjs_cx = GjsContextPrivate::from_cx(cx);
+
+ JS::RootedObject global(cx, gjs_cx->internal_global());
+ JSAutoRealm ar(cx, global);
+
+ JS::RootedValue hookValue(
+ cx, gjs_get_global_slot(global, GjsInternalGlobalSlot::IMPORT_HOOK));
+
+ JS::AutoValueArray<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);
+}
+
bool gjs_populate_module_meta(JSContext* m_cx,
JS::Handle<JS::Value> private_ref,
JS::Handle<JSObject*> meta_object) {
diff --git a/gjs/module.h b/gjs/module.h
index d4ecf3dc..433d5d73 100644
--- a/gjs/module.h
+++ b/gjs/module.h
@@ -122,6 +122,11 @@ GJS_JSAPI_RETURN_CONVENTION
JSObject* gjs_module_resolve(JSContext* cx, JS::HandleValue mod_val,
JS::HandleString specifier);
+bool gjs_dynamic_module_resolve(JSContext* cx,
+ JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSString*> aSpecifier,
+ JS::Handle<JSObject*> aPromise);
+
bool gjs_populate_module_meta(JSContext* m_cx,
JS::Handle<JS::Value> private_ref,
JS::Handle<JSObject*> meta_object);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]