[gjs/esm/dynamic-imports: 3/3] Add dynamic import support to scripts
- From: Philip Chimento <pchimento src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/esm/dynamic-imports: 3/3] Add dynamic import support to scripts
- Date: Tue, 9 Feb 2021 03:49:14 +0000 (UTC)
commit 81e3d313b40c618a924df07f1b147ca8909cf2eb
Author: Evan Welsh <contact evanwelsh com>
Date: Fri Feb 5 21:51:41 2021 -0800
Add dynamic import support to scripts
To allow import() from scripts we must set script privates
with an object similar to the ModulePrivate class.
gjs/context.cpp | 8 ++++
gjs/module.cpp | 39 ++++++++++++++-
gjs/module.h | 3 ++
installed-tests/js/jsunit.gresources.xml | 1 +
installed-tests/js/modules/dynamic.js | 10 ++++
installed-tests/js/testImporter.js | 13 +++++
modules/internal/loader.js | 3 +-
test/gjs-tests.cpp | 81 ++++++++++++++++++++++++++++++++
8 files changed, 155 insertions(+), 3 deletions(-)
---
diff --git a/gjs/context.cpp b/gjs/context.cpp
index 64ef9605..379f623d 100644
--- a/gjs/context.cpp
+++ b/gjs/context.cpp
@@ -1253,6 +1253,14 @@ bool GjsContextPrivate::eval_with_scope(JS::HandleObject scope_object,
JS::CompileOptions options(m_cx);
options.setFileAndLine(filename, 1);
+ GjsAutoUnref<GFile> file = g_file_new_for_commandline_arg(filename);
+ GjsAutoChar uri = g_file_get_uri(file);
+ JS::RootedObject priv(m_cx, gjs_script_module_build_private(m_cx, uri));
+ if (!priv)
+ return false;
+
+ options.setPrivateValue(JS::ObjectValue(*priv));
+
if (!JS::Evaluate(m_cx, scope_chain, options, buf, retval))
return false;
diff --git a/gjs/module.cpp b/gjs/module.cpp
index 63efe59a..61bc4e9a 100644
--- a/gjs/module.cpp
+++ b/gjs/module.cpp
@@ -94,7 +94,7 @@ class GjsScriptModule {
GJS_JSAPI_RETURN_CONVENTION
bool evaluate_import(JSContext* cx, JS::HandleObject module,
const char* script, ssize_t script_len,
- const char* filename) {
+ const char* filename, const char* uri) {
std::u16string utf16_string =
gjs_utf8_script_to_utf16(script, script_len);
// COMPAT: This could use JS::SourceText<mozilla::Utf8Unit> directly,
@@ -114,6 +114,9 @@ class GjsScriptModule {
JS::CompileOptions options(cx);
options.setFileAndLine(filename, 1);
+ JS::RootedObject priv(cx, build_private(cx, uri));
+ options.setPrivateValue(JS::ObjectValue(*priv));
+
JS::RootedValue ignored_retval(cx);
if (!JS::Evaluate(cx, scope_chain, options, buf, &ignored_retval))
return false;
@@ -145,7 +148,8 @@ class GjsScriptModule {
g_assert(script);
GjsAutoChar full_path = g_file_get_parse_name(file);
- return evaluate_import(cx, module, script, script_len, full_path);
+ GjsAutoChar uri = g_file_get_uri(file);
+ return evaluate_import(cx, module, script, script_len, full_path, uri);
}
/* JSClass operations */
@@ -215,6 +219,22 @@ class GjsScriptModule {
};
public:
+ /*
+ * Creates a JS object to pass to JS::CompileOptions as a script's private.
+ */
+ GJS_JSAPI_RETURN_CONVENTION
+ static JSObject* build_private(JSContext* cx, const char* script_uri) {
+ JS::RootedObject priv(cx, JS_NewPlainObject(cx));
+ const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
+
+ JS::RootedValue val(cx);
+ if (!gjs_string_from_utf8(cx, script_uri, &val) ||
+ !JS_SetPropertyById(cx, priv, atoms.uri(), val))
+ return nullptr;
+
+ return priv;
+ }
+
/* Carries out the import operation */
GJS_JSAPI_RETURN_CONVENTION
static JSObject *
@@ -234,6 +254,21 @@ class GjsScriptModule {
}
};
+/**
+ * gjs_script_module_build_private:
+ * @param cx the #JSContext
+ * @param uri the URI this script module is loaded from
+ *
+ * @brief To support dynamic imports from scripts, we need to provide private
+ * data when we compile scripts which is compatible with our module resolution
+ * hooks in modules/internal/loader.js
+ *
+ * @returns a JSObject which can be used for a JSScript's private data.
+ */
+JSObject* gjs_script_module_build_private(JSContext* cx, const char* uri) {
+ return GjsScriptModule::build_private(cx, uri);
+}
+
/**
* gjs_module_import:
* @cx: the JS context
diff --git a/gjs/module.h b/gjs/module.h
index dfe6f18c..39d550b4 100644
--- a/gjs/module.h
+++ b/gjs/module.h
@@ -21,6 +21,9 @@ gjs_module_import(JSContext *cx,
const char *name,
GFile *file);
+GJS_JSAPI_RETURN_CONVENTION
+JSObject* gjs_script_module_build_private(JSContext* cx, const char* uri);
+
GJS_JSAPI_RETURN_CONVENTION
JSObject* gjs_get_native_registry(JSObject* global);
diff --git a/installed-tests/js/jsunit.gresources.xml b/installed-tests/js/jsunit.gresources.xml
index 624ac738..b635c50c 100644
--- a/installed-tests/js/jsunit.gresources.xml
+++ b/installed-tests/js/jsunit.gresources.xml
@@ -13,6 +13,7 @@
<file>modules/badOverrides/Regress.js</file>
<file>modules/badOverrides/WarnLib.js</file>
<file>modules/data.txt</file>
+ <file>modules/dynamic.js</file>
<file>modules/importmeta.js</file>
<file>modules/exports.js</file>
<file>modules/foobar.js</file>
diff --git a/installed-tests/js/modules/dynamic.js b/installed-tests/js/modules/dynamic.js
new file mode 100644
index 00000000..383ee7a0
--- /dev/null
+++ b/installed-tests/js/modules/dynamic.js
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 Evan Welsh
+
+/* exported test */
+
+async function test() {
+ const {say} = await import('./say.js');
+
+ return say('I did it!');
+}
diff --git a/installed-tests/js/testImporter.js b/installed-tests/js/testImporter.js
index b9c519c5..58e9e9c5 100644
--- a/installed-tests/js/testImporter.js
+++ b/installed-tests/js/testImporter.js
@@ -237,4 +237,17 @@ describe('Importer', function () {
expect(imports[0]).not.toBeDefined();
expect(imports.foobar[0]).not.toBeDefined();
});
+
+ it('scripts support relative dynamic imports', async function () {
+ const {say} = await import('./modules/say.js');
+
+ expect(typeof say).toBe('function');
+ expect(say('hello')).toBe('<( hello )');
+ });
+
+ it('imported scripts support relative dynamic imports', async function () {
+ const response = await imports.dynamic.test();
+
+ expect(response).toBe('<( I did it! )');
+ });
});
diff --git a/modules/internal/loader.js b/modules/internal/loader.js
index f28814c9..63a1c405 100644
--- a/modules/internal/loader.js
+++ b/modules/internal/loader.js
@@ -124,7 +124,8 @@ class ModuleLoader extends InternalModuleLoader {
}
moduleResolveAsyncHook(importingModulePriv, specifier) {
- // importingModulePriv may be falsy in the case of gjs_context_eval()
+ // importingModulePriv should never be missing. If it is then a JSScript
+ // is missing a private object
if (!importingModulePriv || !importingModulePriv.uri)
throw new ImportError('Cannot resolve relative imports from an unknown file.');
diff --git a/test/gjs-tests.cpp b/test/gjs-tests.cpp
index cd79d100..d8a32102 100644
--- a/test/gjs-tests.cpp
+++ b/test/gjs-tests.cpp
@@ -112,6 +112,81 @@ gjstest_test_func_gjs_context_construct_eval(void)
g_object_unref (context);
}
+static void gjstest_test_func_gjs_context_eval_dynamic_import() {
+ GjsAutoUnref<GjsContext> gjs = gjs_context_new();
+ GError* error = NULL;
+ int status;
+
+ bool ok = gjs_context_eval(gjs, R"js(
+ import('system')
+ .catch(logError)
+ .finally(() => imports.mainloop.quit());
+ imports.mainloop.run();
+ )js",
+ -1, "<main>", &status, &error);
+
+ g_assert_true(ok);
+ g_assert_no_error(error);
+}
+
+static void gjstest_test_func_gjs_context_eval_dynamic_import_relative() {
+ GjsAutoUnref<GjsContext> gjs = gjs_context_new();
+ GError* error = NULL;
+ int status;
+
+ bool ok = g_file_set_contents("num.js", "export default 77;", -1, &error);
+
+ g_assert_true(ok);
+ g_assert_no_error(error);
+
+ ok = gjs_context_eval(gjs, R"js(
+ let num;
+ import('./num.js')
+ .then(module => (num = module.default))
+ .catch(logError)
+ .finally(() => imports.mainloop.quit());
+ imports.mainloop.run();
+ num;
+ )js",
+ -1, "<main>", &status, &error);
+
+ g_assert_true(ok);
+ g_assert_no_error(error);
+ g_assert_cmpint(status, ==, 77);
+
+ g_unlink("num.js");
+}
+
+static void gjstest_test_func_gjs_context_eval_dynamic_import_bad() {
+ GjsAutoUnref<GjsContext> gjs = gjs_context_new();
+ GError* error = NULL;
+ int status;
+
+ g_test_expect_message("Gjs", G_LOG_LEVEL_WARNING,
+ "*Unknown module: 'badmodule'*");
+
+ bool ok = gjs_context_eval(gjs, R"js(
+ let isBad = false;
+ import('badmodule')
+ .catch(err => {
+ logError(err);
+ isBad = true;
+ })
+ .finally(() => imports.mainloop.quit());
+ imports.mainloop.run();
+
+ if (isBad) imports.system.exit(10);
+ )js",
+ -1, "<main>", &status, &error);
+
+ g_assert_false(ok);
+ g_assert_cmpuint(status, ==, 10);
+
+ g_test_assert_expected_messages();
+
+ g_clear_error(&error);
+}
+
static void gjstest_test_func_gjs_context_eval_non_zero_terminated(void) {
GjsAutoUnref<GjsContext> gjs = gjs_context_new();
GError* error = NULL;
@@ -765,6 +840,12 @@ main(int argc,
g_test_add_func("/gjs/context/construct/destroy", gjstest_test_func_gjs_context_construct_destroy);
g_test_add_func("/gjs/context/construct/eval", gjstest_test_func_gjs_context_construct_eval);
+ g_test_add_func("/gjs/context/eval/dynamic-import",
+ gjstest_test_func_gjs_context_eval_dynamic_import);
+ g_test_add_func("/gjs/context/eval/dynamic-import/relative",
+ gjstest_test_func_gjs_context_eval_dynamic_import_relative);
+ g_test_add_func("/gjs/context/eval/dynamic-import/bad",
+ gjstest_test_func_gjs_context_eval_dynamic_import_bad);
g_test_add_func("/gjs/context/eval/non-zero-terminated",
gjstest_test_func_gjs_context_eval_non_zero_terminated);
g_test_add_func("/gjs/context/exit", gjstest_test_func_gjs_context_exit);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]