[gjs/esm-local-imports: 5/6] Cleanup internal scripts, add module system.
- From: Evan Welsh <ewlsh src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/esm-local-imports: 5/6] Cleanup internal scripts, add module system.
- Date: Mon, 15 Jun 2020 16:41:18 +0000 (UTC)
commit 0a80c0a0b53c948a0a3d46b47e59386dae868b34
Author: Evan Welsh <noreply evanwelsh com>
Date: Sun Jun 14 12:28:56 2020 -0500
Cleanup internal scripts, add module system.
gi/repo.cpp | 3 +-
gjs/.eslintrc.yml | 2 +-
gjs/atoms.h | 1 +
gjs/console.cpp | 8 +-
gjs/context.cpp | 9 +-
gjs/global.cpp | 7 +-
gjs/internal.cpp | 9 +-
gjs/internal.h | 4 +-
gjs/internal/errorTypes.js | 31 +++++
gjs/internal/module.js | 265 ++++++++++++++++++++++++++++++++++++
gjs/internal/module/loaders/file.js | 26 ++++
gjs/module.js | 218 -----------------------------
js.gresource.xml | 6 +-
13 files changed, 354 insertions(+), 235 deletions(-)
---
diff --git a/gi/repo.cpp b/gi/repo.cpp
index ca306366..69c1df2f 100644
--- a/gi/repo.cpp
+++ b/gi/repo.cpp
@@ -677,7 +677,8 @@ static JSObject* lookup_internal_namespace(JSContext* cx,
// The internal global only supports GObject, Gio, GLib, and private
// namespaces.
if (ns_name == atoms.gobject() || ns_name == atoms.gio() ||
- ns_name == atoms.glib() || ns_name == atoms.private_ns_marker()) {
+ ns_name == atoms.glib() || ns_name == atoms.soup() ||
+ ns_name == atoms.private_ns_marker()) {
JS::RootedObject retval(cx);
if (!gjs_object_require_property(
diff --git a/gjs/.eslintrc.yml b/gjs/.eslintrc.yml
index 748035e5..9b4f5542 100644
--- a/gjs/.eslintrc.yml
+++ b/gjs/.eslintrc.yml
@@ -19,4 +19,4 @@ globals:
lookupInternalModule: readonly
registerInternalModule: readonly
setModuleResolveHook: readonly
- getModuleUri: readonly
\ No newline at end of file
+ getModuleURI: readonly
\ No newline at end of file
diff --git a/gjs/atoms.h b/gjs/atoms.h
index baf1e681..a996b5b2 100644
--- a/gjs/atoms.h
+++ b/gjs/atoms.h
@@ -71,6 +71,7 @@
macro(prototype, "prototype") \
macro(search_path, "searchPath") \
macro(signal_id, "signalId") \
+ macro(soup, "Soup") \
macro(stack, "stack") \
macro(to_string, "toString") \
macro(value_of, "valueOf") \
diff --git a/gjs/console.cpp b/gjs/console.cpp
index 428fe1c4..8a80a93a 100644
--- a/gjs/console.cpp
+++ b/gjs/console.cpp
@@ -202,15 +202,15 @@ int define_argv_and_eval_script(GjsContext* js_context, int argc,
int code;
if (exec_as_module) {
GjsAutoUnref<GFile> output = g_file_new_for_commandline_arg(filename);
- char* full_path = g_file_get_path(output);
- if (!gjs_context_register_module(js_context, full_path, full_path,
- script, len, &error)) {
+ char* uri = g_file_get_uri(output);
+ if (!gjs_context_register_module(js_context, uri, uri, script, len,
+ &error)) {
g_printerr("%s\n", error->message);
code = 1;
}
uint8_t code_8 = 0;
- if (!gjs_context_eval_module(js_context, full_path, &code_8, &error)) {
+ if (!gjs_context_eval_module(js_context, uri, &code_8, &error)) {
code = code_8;
if (!g_error_matches(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT))
g_critical("%s", error->message);
diff --git a/gjs/context.cpp b/gjs/context.cpp
index a4d14d71..109bbb38 100644
--- a/gjs/context.cpp
+++ b/gjs/context.cpp
@@ -546,9 +546,14 @@ GjsContextPrivate::GjsContextPrivate(JSContext* cx, GjsContext* public_context)
m_global = global;
// Load internal script *must* be called from the internal realm.
- if (!gjs_load_internal_script(cx, "module")) {
+
+ // TODO(ewlsh): Consider moving internal imports into JavaScript
+
+ if (!gjs_load_internal_script(cx, "errorTypes") ||
+ !gjs_load_internal_script(cx, "module") ||
+ !gjs_load_internal_script(cx, "module/loaders/file")) {
gjs_log_exception(cx);
- g_warning("Failed to load internal dynamic module hooks.");
+ g_error("Failed to load internal module loaders.");
}
auto realm = JS::EnterRealm(m_cx, global);
diff --git a/gjs/global.cpp b/gjs/global.cpp
index 88cb420c..381deef4 100644
--- a/gjs/global.cpp
+++ b/gjs/global.cpp
@@ -448,7 +448,7 @@ class GjsDebuggerGlobal {
class GjsInternalGlobal {
static constexpr JSFunctionSpec static_funcs[] = {
- JS_FN("getModuleUri", GetModuleUri, 1, 0),
+ JS_FN("getModuleURI", GetModuleURI, 1, 0),
JS_FN("compileAndEvalModule", CompileAndEvalModule, 1, 0),
JS_FN("debug", Debug, 1, 0),
JS_FN("lookupInternalModule", LookupInternalModule, 1, 0),
@@ -505,6 +505,8 @@ class GjsInternalGlobal {
!g_irepository_require(nullptr, "GLib", "2.0",
GIRepositoryLoadFlags(0), &error) ||
!g_irepository_require(nullptr, "Gio", "2.0",
+ GIRepositoryLoadFlags(0), &error) ||
+ !g_irepository_require(nullptr, "Soup", "2.4",
GIRepositoryLoadFlags(0), &error)) {
gjs_throw_gerror_message(cx, error);
g_error_free(error);
@@ -514,6 +516,7 @@ class GjsInternalGlobal {
JS::RootedObject gobject(cx, gjs_create_ns(cx, "GObject"));
JS::RootedObject glib(cx, gjs_create_ns(cx, "GLib"));
JS::RootedObject gio(cx, gjs_create_ns(cx, "Gio"));
+ JS::RootedObject soup(cx, gjs_create_ns(cx, "Soup"));
JS::RootedObject privateNS(cx, JS_NewPlainObject(cx));
if (!JS_DefinePropertyById(cx, global, atoms.private_ns_marker(),
@@ -523,6 +526,8 @@ class GjsInternalGlobal {
!JS_DefinePropertyById(cx, global, atoms.glib(), glib,
JSPROP_PERMANENT) ||
!JS_DefinePropertyById(cx, global, atoms.gio(), gio,
+ JSPROP_PERMANENT) ||
+ !JS_DefinePropertyById(cx, global, atoms.soup(), soup,
JSPROP_PERMANENT)) {
return false;
}
diff --git a/gjs/internal.cpp b/gjs/internal.cpp
index e5c7ed7c..ffd0abe5 100644
--- a/gjs/internal.cpp
+++ b/gjs/internal.cpp
@@ -62,8 +62,8 @@
using AutoGFile = GjsAutoUnref<GFile>;
bool gjs_load_internal_script(JSContext* cx, const char* identifier) {
- GjsAutoChar full_path(
- g_strdup_printf("resource://org/gnome/gjs/gjs/%s.js", identifier));
+ GjsAutoChar full_path(g_strdup_printf(
+ "resource://org/gnome/gjs/gjs/internal/%s.js", identifier));
AutoGFile gfile(g_file_new_for_uri(full_path));
char* script_text_raw;
@@ -97,6 +97,7 @@ bool gjs_load_internal_script(JSContext* cx, const char* identifier) {
JS::RootedObject internal_global(cx, gjs_get_internal_global(cx));
JSAutoRealm ar(cx, internal_global);
+
JS::RootedValue ignored_retval(cx);
JS::RootedObject module(cx, JS_NewPlainObject(cx));
JS::RootedObjectVector scope_chain(cx);
@@ -128,9 +129,9 @@ bool gjs_load_internal_script(JSContext* cx, const char* identifier) {
return true;
}
-bool GetModuleUri(JSContext* cx, unsigned argc, JS::Value* vp) {
+bool GetModuleURI(JSContext* cx, unsigned argc, JS::Value* vp) {
JS::CallArgs args = CallArgsFromVp(argc, vp);
- if (!args.requireAtLeast(cx, "getModuleUri", 1)) {
+ if (!args.requireAtLeast(cx, "getModuleURI", 1)) {
return false;
}
diff --git a/gjs/internal.h b/gjs/internal.h
index 98aa7f8b..475b6ce2 100644
--- a/gjs/internal.h
+++ b/gjs/internal.h
@@ -60,7 +60,7 @@ bool LookupModule(JSContext* cx, unsigned argc, JS::Value* vp);
// debug(msg: string)
bool Debug(JSContext* cx, unsigned argc, JS::Value* vp);
-// getModuleUri(module): string
-bool GetModuleUri(JSContext* cx, unsigned argc, JS::Value* vp);
+// getModuleURI(module): string
+bool GetModuleURI(JSContext* cx, unsigned argc, JS::Value* vp);
#endif // GJS_INTERNAL_H_
diff --git a/gjs/internal/errorTypes.js b/gjs/internal/errorTypes.js
new file mode 100644
index 00000000..94a6c355
--- /dev/null
+++ b/gjs/internal/errorTypes.js
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2020 Evan Welsh <contact evanwelsh com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+class ImportError extends Error {
+ constructor(message) {
+ super(message);
+
+ this.name = 'ImportError';
+ }
+}
+
+globalThis.ImportError = ImportError;
diff --git a/gjs/internal/module.js b/gjs/internal/module.js
new file mode 100644
index 00000000..3953cd99
--- /dev/null
+++ b/gjs/internal/module.js
@@ -0,0 +1,265 @@
+/*
+ * Copyright (c) 2020 Evan Welsh <contact evanwelsh com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/* global debug, Soup, ImportError */
+
+if (typeof ImportError !== 'function') {
+ throw new Error('ImportError is not defined in module loader.');
+}
+
+// NOTE: Gio, GLib, and GObject have no overrides.
+
+function isRelativePath(id) {
+ // Check if the path is relative.
+ return id.startsWith('./') || id.startsWith('../');
+}
+
+const allowedRelatives = ["file", "resource"];
+
+const relativeResolvers = new Map();
+const loaders = new Map();
+
+function registerScheme(...schemes) {
+ function forEach(fn, ...args) {
+ schemes.forEach(s => fn(s, ...args));
+ }
+
+ const schemeBuilder = {
+ relativeResolver(handler) {
+ forEach((scheme) => {
+ allowedRelatives.push(scheme);
+ relativeResolvers.set(scheme, handler);
+ });
+
+ return schemeBuilder;
+ },
+ loader(handler) {
+ forEach(scheme => {
+ loaders.set(scheme, handler);
+ });
+
+ return schemeBuilder;
+ }
+ };
+
+ return Object.freeze(schemeBuilder);
+}
+
+globalThis.registerScheme = registerScheme;
+
+function parseURI(uri) {
+ const parsed = Soup.URI.new(uri);
+
+ if (!parsed) {
+ return null;
+ }
+
+ return {
+ raw: uri,
+ query: parsed.query ? Soup.form_decode(parsed.query) : {},
+ rawQuery: parsed.query,
+ scheme: parsed.scheme,
+ host: parsed.host,
+ port: parsed.port,
+ path: parsed.path,
+ fragment: parsed.fragment
+ };
+
+}
+
+/**
+ * @type {Set<string>}
+ *
+ * The set of "module" URIs (the module search path)
+ */
+const moduleURIs = new Set();
+
+function registerModuleURI(uri) {
+ moduleURIs.add(uri);
+}
+
+// Always let ESM-specific modules take priority over core modules.
+registerModuleURI('resource:///org/gnome/gjs/modules/esm/');
+registerModuleURI('resource:///org/gnome/gjs/modules/core/');
+
+/**
+ * @param {string} specifier
+ */
+function buildInternalURIs(specifier) {
+ const builtURIs = [];
+
+ for (const uri of moduleURIs) {
+ const builtURI = `${uri}/${specifier}.js`;
+
+ debug(`Built internal URI ${builtURI} with ${specifier} for ${uri}.`);
+
+ builtURIs.push(builtURI);
+ }
+
+ return builtURIs
+}
+
+function resolveRelativePath(moduleURI, relativePath) {
+ // If a module has a path, we'll have stored it in the host field
+ if (!moduleURI) {
+ throw new ImportError('Cannot import from relative path when module path is unknown.');
+ }
+
+ debug(`moduleURI: ${moduleURI}`);
+
+ const parsed = parseURI(moduleURI);
+
+ // Handle relative imports from URI-based modules.
+ if (parsed) {
+ const resolver = relativeResolvers.get(parsed.scheme);
+
+ if (resolver) {
+ return resolver(parsed, relativePath);
+ } else {
+ throw new ImportError(
+ `Relative imports can only occur from the following URI schemes: ${
+ Array.from(relativeResolvers.keys()).map(s => `${s}://`).join(', ')
+ }`);
+ }
+ } else {
+ throw new ImportError(`Module has invalid URI: ${moduleURI}`);
+ }
+}
+
+function loadURI(uri) {
+ debug(`URI: ${uri.raw}`);
+
+ if (uri.scheme) {
+ const loader = loaders.get(uri.scheme);
+
+ if (loader) {
+ return 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 };
+}
+
+function resolveModule(specifier, moduleURI) {
+ // Check if the module has already been loaded
+ //
+ // Order:
+ // - Local imports
+ // - Internal imports
+
+ debug(`Resolving: ${specifier}`);
+
+ let lookup_module = lookupModule(specifier);
+
+ if (lookup_module)
+ return lookup_module;
+
+ lookup_module = lookupInternalModule(specifier);
+
+ if (lookup_module)
+ return lookup_module;
+
+ // 1) Resolve path and URI-based imports.
+
+ const resolved = resolveSpecifier(specifier, moduleURI);
+
+ if (resolved) {
+ const { output, uri } = resolved;
+
+ debug(`Full path found: ${uri}`);
+
+ lookup_module = lookupModule(uri);
+
+ // Check if module is already loaded (relative handling)
+ if (lookup_module)
+ return lookup_module;
+
+ const text = output;
+
+ if (!registerModule(uri, uri, text, text.length, false))
+ throw new ImportError(`Failed to register module: ${uri}`);
+
+
+ return lookupModule(uri);
+ }
+
+ // 2) Resolve internal imports.
+
+ const uri = buildInternalURIs(specifier).find((uri) => {
+ let file = Gio.File.new_for_uri(uri);
+
+ return file && file.query_exists(null);
+ });
+
+ if (!uri)
+ throw new ImportError(`Attempted to load unregistered global module: ${specifier}`);
+
+ const text = loaders.get('resource')(parseURI(uri));
+
+ if (!registerInternalModule(specifier, uri, text, text.length))
+ return null;
+
+ return lookupInternalModule(specifier);
+}
+
+setModuleResolveHook((referencingInfo, specifier) => {
+ debug('Starting module import...');
+ const uri = getModuleURI(referencingInfo);
+
+ if (uri) {
+ debug(`Found base URI: ${uri}`);
+ }
+
+ return resolveModule(specifier, uri);
+});
diff --git a/gjs/internal/module/loaders/file.js b/gjs/internal/module/loaders/file.js
new file mode 100644
index 00000000..fc0f4f2f
--- /dev/null
+++ b/gjs/internal/module/loaders/file.js
@@ -0,0 +1,26 @@
+function fromBytes(bytes) {
+ return ByteUtils.toString(bytes, 'utf-8');
+}
+
+function loadFileSync(output, full_path) {
+ try {
+ const [, bytes] = output.load_contents(null);
+ return fromBytes(bytes);
+ } catch (error) {
+ throw new Error(`Unable to load file from: ${full_path}`);
+ }
+}
+
+registerScheme("file", "resource")
+ .relativeResolver((moduleURI, relativePath) => {
+ let module_file = Gio.File.new_for_uri(moduleURI.raw);
+ let module_parent_file = module_file.get_parent();
+
+ let output = module_parent_file.resolve_relative_path(relativePath);
+
+ return output.get_uri();
+ }).loader(uri => {
+ const file = Gio.File.new_for_uri(uri.raw);
+
+ return loadFileSync(file, file.get_uri());
+ });
diff --git a/js.gresource.xml b/js.gresource.xml
index dd5a5341..10653e86 100644
--- a/js.gresource.xml
+++ b/js.gresource.xml
@@ -2,8 +2,10 @@
<gresources>
<gresource prefix="/org/gnome/gjs">
<!-- Internal scripts -->
- <file>gjs/module.js</file>
-
+ <file>gjs/internal/errorTypes.js</file>
+ <file>gjs/internal/module.js</file>
+ <file>gjs/internal/module/loaders/file.js</file>
+
<!-- ESM-based modules -->
<file>modules/esm/gi.js</file>
<file>modules/esm/system.js</file>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]