diff --git a/README.md b/README.md index c32a4aa..bac575a 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ It may be useful for standalone scripts that do not require a `package.json`. Al - [Browser](#browser) - [Deno](#deno) - [Bun](#bun) + - [GJS (GNOME JavaScript)](#gjs-gnome-javascript) - [Network imports](#network-imports) - [Independent Scripts](#independent-scripts) - [`use-m` and `command-stream`](#use-m-and-command-stream) @@ -42,7 +43,8 @@ It may be useful for standalone scripts that do not require a `package.json`. Al - **Dynamic package loading**: In `node.js`, `use-m` loads and imports npm packages on-demand with **global installation** (using `npm i -g` with separate alias for each version), making them available across projects and reusable without needing to reinstall each time. In case of a browser `use-m` loads npm packages directly from CDNs (by default `esm.sh` is used). - **Version-safe imports**: Allows multiple versions of the same library to coexist without conflicts, so you can specify any version for each import (usage) without affecting other scripts or other usages (imports) in the same script. - **No more `require`, `import`, or `package.json`**: With `use-m`, traditional module loading approaches like `require()`, `import` statements, and `package.json` dependencies become effectively obsolete. You can dynamically load any module at runtime without pre-declaring dependencies in separate file. This enables truly self-contained `.mjs` files that can effectively replace shell scripts. -- **Built-in modules emulation**: Provides emulation for Node.js built-in modules across all environments (browser, Node.js, Bun, Deno), ensuring consistent behavior regardless of the runtime. +- **Built-in modules emulation**: Provides emulation for Node.js built-in modules across all environments (browser, Node.js, Bun, Deno, GJS), ensuring consistent behavior regardless of the runtime. +- **GJS native support**: Full support for GJS (GNOME JavaScript) runtime with native access to GObject introspection libraries via `gi://` protocol and GJS built-in modules. - **Relative path resolution**: Supports `./ ` and `../` paths for loading local JavaScript and JSON files relative to the executing file, working seamlessly even in browser environments. ## Usage @@ -155,6 +157,45 @@ Run with Bun: bun run example.mjs ``` +### GJS (GNOME JavaScript) + +`use-m` works seamlessly with GJS! It automatically detects the GJS runtime and provides support for both legacy imports and modern ES modules, along with GJS-specific features. + +```javascript +// Import use-m from CDN (works in GJS with ES modules) +const { use } = await import('https://esm.sh/use-m'); + +// Use any npm package +const _ = await use('lodash@4.17.21'); +console.log(`_.add(1, 2) = ${_.add(1, 2)}`); + +// Import multiple packages +const [lodash3, lodash4] = await use.all('lodash@3', 'lodash@4'); + +// Use GJS built-in modules +const consoleModule = await use('console'); +const urlModule = await use('url'); + +// Use GObject introspection libraries with gi:// protocol +const GLib = await use('gi://GLib'); +const Gtk = await use('gi://Gtk?version=4.0'); +``` + +Run with GJS: +```bash +gjs example.mjs +``` + +For legacy GJS environments, you can also use the eval approach: +```javascript +const useJs = await (await fetch('https://unpkg.com/use-m/use.js')).text(); +const { use } = eval(useJs); + +// Now you can use GJS built-ins via use-m +const cairo = await use('cairo'); +const system = await use('system'); +``` + ### Network imports It is possible to use `--experimental-network-imports` to enable the same style of imports as in browser version. See [the example](https://github.com/link-foundation/use-m/blob/main/examples/network-imports/index.mjs). diff --git a/examples/gjs/example.mjs b/examples/gjs/example.mjs new file mode 100644 index 0000000..fa991d8 --- /dev/null +++ b/examples/gjs/example.mjs @@ -0,0 +1,44 @@ +// GJS example showing use-m working with GJS (GNOME JavaScript) runtime +// Run with: gjs example.mjs + +// Import use-m from CDN (works in GJS with ES modules) +const { use } = await import('https://esm.sh/use-m'); + +console.log('🏠 Testing use-m with GJS (GNOME JavaScript)...'); + +try { + // Test basic package import + console.log('📦 Importing lodash...'); + const _ = await use('lodash@4.17.21'); + const result = _.add(1, 2); + console.log(`✅ _.add(1, 2) = ${result}`); + + // Test scoped package + console.log('📦 Importing @octokit/core...'); + const { Octokit } = await use('@octokit/core@6.1.5'); + const octokit = new Octokit(); + console.log('✅ Octokit instance created successfully'); + + // Test use.all for multiple packages + console.log('📦 Importing multiple packages...'); + const [lodash3, lodash4] = await use.all('lodash@3', 'lodash@4'); + console.log(`✅ Multiple versions: lodash3.add(1,2)=${lodash3.add(1,2)}, lodash4.add(1,2)=${lodash4.add(1,2)}`); + + // Test GJS built-in module support (console) + console.log('🏠 Testing GJS built-in module support...'); + const consoleModule = await use('console'); + console.log('✅ Console module imported successfully'); + + // Test URL support (available in GJS) + console.log('🔗 Testing URL module...'); + const urlModule = await use('url'); + const testUrl = new urlModule.URL('https://github.com/link-foundation/use-m'); + console.log(`✅ URL parsed: ${testUrl.hostname}`); + + console.log('🎉 All tests passed! GJS support is working correctly.'); +} catch (error) { + console.error('❌ Error:', error.message); + console.error(error.stack); + // In GJS, we would use imports.system.exit but it's not always available + // So we just let the script end naturally +} \ No newline at end of file diff --git a/examples/gjs/legacy-example.js b/examples/gjs/legacy-example.js new file mode 100644 index 0000000..411fb51 --- /dev/null +++ b/examples/gjs/legacy-example.js @@ -0,0 +1,59 @@ +// GJS legacy example showing use-m working with GJS legacy imports system +// Run with: gjs legacy-example.js +// Note: This example uses the legacy imports object rather than ES modules + +// For legacy GJS, we need to use the eval approach since we can't use await import() +// This demonstrates how use-m works even with the legacy imports object +const useJs = await (await fetch('https://unpkg.com/use-m/use.js')).text(); +const { use } = eval(useJs); + +console.log('🏠 Testing use-m with GJS (GNOME JavaScript) legacy imports...'); + +try { + // Test basic package import + console.log('📦 Importing lodash...'); + const _ = await use('lodash@4.17.21'); + const result = _.add(1, 2); + console.log(`✅ _.add(1, 2) = ${result}`); + + // Test GJS built-in module support (console) + console.log('🏠 Testing GJS built-in module support...'); + const consoleModule = await use('console'); + console.log('✅ Console module imported successfully'); + + // Test GJS built-in modules using legacy imports (if available) + if (typeof imports !== 'undefined') { + console.log('🏠 Testing GJS legacy built-in module access...'); + + // Test GJS built-in via use-m + try { + const cairoModule = await use('cairo'); + console.log('✅ Cairo module imported via use-m successfully'); + } catch (e) { + console.log('⚠️ Cairo module not available (this is expected in some GJS environments)'); + } + + // Test GI library access via use-m with gi:// protocol + try { + const GLib = await use('gi://GLib'); + console.log('✅ GLib imported via gi:// protocol successfully'); + console.log(`✅ GLib version info available: ${typeof GLib !== 'undefined'}`); + } catch (e) { + console.log('⚠️ GLib via gi:// not available (this is expected in some GJS environments)'); + console.log(' Error:', e.message); + } + + // Test with version specification + try { + const Gtk = await use('gi://Gtk?version=4.0'); + console.log('✅ Gtk 4.0 imported via gi:// protocol successfully'); + } catch (e) { + console.log('⚠️ Gtk 4.0 not available (this is expected in some GJS environments)'); + } + } + + console.log('🎉 All tests passed! GJS legacy support is working correctly.'); +} catch (error) { + console.error('❌ Error:', error.message); + console.error(error.stack); +} \ No newline at end of file diff --git a/examples/gjs/test-local.mjs b/examples/gjs/test-local.mjs new file mode 100644 index 0000000..2717183 --- /dev/null +++ b/examples/gjs/test-local.mjs @@ -0,0 +1,50 @@ +// Test the local implementation with GJS +// Run with: gjs test-local.mjs + +// Import the local use-m implementation +const { use } = await import('../../use.mjs'); + +console.log('🏠 Testing local use-m implementation with GJS...'); + +try { + // Verify GJS detection works + if (typeof imports !== 'undefined') { + console.log('✅ GJS runtime detected (legacy imports available)'); + console.log(` Imports object exists: ${typeof imports === 'object'}`); + if (imports.gi) { + console.log(' GObject introspection available: ✅'); + } else { + console.log(' GObject introspection available: ❌'); + } + } else { + console.log('❌ GJS runtime not detected (legacy imports not available)'); + console.log(' This might be a modern GJS environment using only ES modules'); + } + + // Test resolver selection by checking internal behavior + console.log('📦 Testing resolver selection...'); + + // We can test built-in module support + try { + const consoleModule = await use('console'); + console.log('✅ Console built-in module resolution works'); + } catch (e) { + console.log('❌ Console built-in module resolution failed:', e.message); + } + + // Test URL built-in module support + try { + const urlModule = await use('url'); + console.log('✅ URL built-in module resolution works'); + } catch (e) { + console.log('❌ URL built-in module resolution failed:', e.message); + } + + console.log('✅ GJS resolver ready'); + + console.log('🎉 GJS support implementation is ready!'); + console.log('💡 To test with network access, run: gjs examples/gjs/example.mjs'); +} catch (error) { + console.error('❌ Error:', error.message); + console.error(error.stack); +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f5b1536..3728513 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "use-m", - "version": "8.13.2", + "version": "8.13.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "use-m", - "version": "8.13.2", + "version": "8.13.6", "license": "UNLICENSED", "bin": { "use": "cli.mjs" diff --git a/tests/gjs-support.test.cjs b/tests/gjs-support.test.cjs new file mode 100644 index 0000000..09746f6 --- /dev/null +++ b/tests/gjs-support.test.cjs @@ -0,0 +1,197 @@ +const { describe, test, expect } = require('../test-adapter.cjs'); + +const moduleName = `[cjs module]`; + +describe(`${moduleName} GJS support`, () => { + test(`${moduleName} GJS resolver should resolve npm packages to esm.sh`, async () => { + const { resolvers } = require('../use.cjs'); + const resolvedPath = await resolvers.gjs('lodash@4.17.21'); + expect(resolvedPath).toBe('https://esm.sh/lodash@4.17.21'); + }); + + test(`${moduleName} GJS resolver should handle scoped packages`, async () => { + const { resolvers } = require('../use.cjs'); + const resolvedPath = await resolvers.gjs('@octokit/core@6.1.5'); + expect(resolvedPath).toBe('https://esm.sh/@octokit/core@6.1.5'); + }); + + test(`${moduleName} GJS resolver should handle module paths`, async () => { + const { resolvers } = require('../use.cjs'); + const resolvedPath = await resolvers.gjs('lodash@4.17.21/add'); + expect(resolvedPath).toBe('https://esm.sh/lodash@4.17.21/add'); + }); + + test(`${moduleName} GJS resolver should handle latest version`, async () => { + const { resolvers } = require('../use.cjs'); + const resolvedPath = await resolvers.gjs('lodash@latest'); + expect(resolvedPath).toBe('https://esm.sh/lodash@latest'); + }); + + test(`${moduleName} GJS resolver should handle gi:// protocol for GObject libraries`, async () => { + const { resolvers } = require('../use.cjs'); + + // Mock the imports object for testing + const originalImports = global.imports; + global.imports = { + gi: { + versions: {}, + GLib: { get_user_name: () => 'testuser' }, + Gtk: { init: () => {} } + } + }; + + try { + const glib = await resolvers.gjs('gi://GLib'); + expect(glib).toBeDefined(); + expect(typeof glib.get_user_name).toBe('function'); + } finally { + // Restore original imports + if (originalImports === undefined) { + delete global.imports; + } else { + global.imports = originalImports; + } + } + }); + + test(`${moduleName} GJS resolver should handle gi:// protocol with version`, async () => { + const { resolvers } = require('../use.cjs'); + + // Mock the imports object for testing + const originalImports = global.imports; + global.imports = { + gi: { + versions: {}, + Gtk: { init: () => {} } + } + }; + + try { + const gtk = await resolvers.gjs('gi://Gtk?version=4.0'); + expect(gtk).toBeDefined(); + expect(global.imports.gi.versions.Gtk).toBe('4.0'); + } finally { + // Restore original imports + if (originalImports === undefined) { + delete global.imports; + } else { + global.imports = originalImports; + } + } + }); + + test(`${moduleName} GJS resolver should handle built-in GJS modules`, async () => { + const { resolvers } = require('../use.cjs'); + + // Mock the imports object for testing + const originalImports = global.imports; + global.imports = { + system: { version: '1.0.0' }, + cairo: { Context: function() {} } + }; + + try { + const systemModule = await resolvers.gjs('system'); + expect(systemModule).toBeDefined(); + expect(systemModule.version).toBe('1.0.0'); + + const cairoModule = await resolvers.gjs('cairo'); + expect(cairoModule).toBeDefined(); + expect(typeof cairoModule.Context).toBe('function'); + } finally { + // Restore original imports + if (originalImports === undefined) { + delete global.imports; + } else { + global.imports = originalImports; + } + } + }); + + test(`${moduleName} GJS resolver should throw error for unavailable gi:// modules`, async () => { + const { resolvers } = require('../use.cjs'); + + // Mock the imports object without the requested library + const originalImports = global.imports; + global.imports = { gi: {} }; + + try { + await expect(resolvers.gjs('gi://NonExistentLib')).rejects.toThrow( + 'GObject introspection library \'NonExistentLib\' not available in this GJS environment.' + ); + } finally { + // Restore original imports + if (originalImports === undefined) { + delete global.imports; + } else { + global.imports = originalImports; + } + } + }); + + test(`${moduleName} GJS resolver should throw error for unavailable built-in modules`, async () => { + const { resolvers } = require('../use.cjs'); + + // Mock the imports object without the requested built-in + const originalImports = global.imports; + global.imports = {}; + + try { + await expect(resolvers.gjs('cairo')).rejects.toThrow( + 'GJS built-in module \'cairo\' not available.' + ); + } finally { + // Restore original imports + if (originalImports === undefined) { + delete global.imports; + } else { + global.imports = originalImports; + } + } + }); + + test(`${moduleName} makeUse should detect GJS runtime when imports global is present`, async () => { + const { makeUse, resolvers } = require('../use.cjs'); + + let mockedImports = false; + if (typeof imports === "undefined") { + global.imports = { gi: {}, system: {} }; + mockedImports = true; + } + try { + const use = await makeUse(); + + // Test by checking which resolver would be used + // We can't directly test the resolver used, but we can test the behavior + // by verifying the resolver produces esm.sh URLs for npm packages + const testModulePath = await resolvers.gjs('lodash@4.17.21'); + expect(testModulePath).toContain('esm.sh'); + } finally { + if (mockedImports) { + delete global.imports; + } + } + }); + + test(`${moduleName} GJS resolver should work with complex package names`, async () => { + const { resolvers } = require('../use.cjs'); + const resolvedPath = await resolvers.gjs('@babel/core@7.23.0/lib/index.js'); + expect(resolvedPath).toBe('https://esm.sh/@babel/core@7.23.0/lib/index.js'); + }); + + test(`${moduleName} GJS resolver should handle packages without versions`, async () => { + const { resolvers } = require('../use.cjs'); + const resolvedPath = await resolvers.gjs('lodash'); + expect(resolvedPath).toBe('https://esm.sh/lodash@latest'); + }); + + test(`${moduleName} makeUse with explicit gjs resolver`, async () => { + const { makeUse, resolvers } = require('../use.cjs'); + const use = await makeUse({ specifierResolver: 'gjs' }); + + // We can't easily test the full import without network access in CI + // But we can verify the resolver would produce the correct URL + const testUrl = await resolvers.gjs('lodash@4.17.21'); + expect(testUrl).toBe('https://esm.sh/lodash@4.17.21'); + }); +}); \ No newline at end of file diff --git a/tests/gjs-support.test.mjs b/tests/gjs-support.test.mjs new file mode 100644 index 0000000..fcc99cc --- /dev/null +++ b/tests/gjs-support.test.mjs @@ -0,0 +1,179 @@ +import { describe, test, expect } from '../test-adapter.mjs'; +import { makeUse, resolvers } from 'use-m/use.mjs'; + +const moduleName = `[${import.meta.url.split('.').pop()} module]`; + +describe(`${moduleName} GJS support`, () => { + test(`${moduleName} GJS resolver should resolve npm packages to esm.sh`, async () => { + const resolvedPath = await resolvers.gjs('lodash@4.17.21'); + expect(resolvedPath).toBe('https://esm.sh/lodash@4.17.21'); + }); + + test(`${moduleName} GJS resolver should handle scoped packages`, async () => { + const resolvedPath = await resolvers.gjs('@octokit/core@6.1.5'); + expect(resolvedPath).toBe('https://esm.sh/@octokit/core@6.1.5'); + }); + + test(`${moduleName} GJS resolver should handle module paths`, async () => { + const resolvedPath = await resolvers.gjs('lodash@4.17.21/add'); + expect(resolvedPath).toBe('https://esm.sh/lodash@4.17.21/add'); + }); + + test(`${moduleName} GJS resolver should handle latest version`, async () => { + const resolvedPath = await resolvers.gjs('lodash@latest'); + expect(resolvedPath).toBe('https://esm.sh/lodash@latest'); + }); + + test(`${moduleName} GJS resolver should handle gi:// protocol for GObject libraries`, async () => { + // Mock the imports object for testing + const originalImports = globalThis.imports; + globalThis.imports = { + gi: { + versions: {}, + GLib: { get_user_name: () => 'testuser' }, + Gtk: { init: () => {} } + } + }; + + try { + const glib = await resolvers.gjs('gi://GLib'); + expect(glib).toBeDefined(); + expect(typeof glib.get_user_name).toBe('function'); + } finally { + // Restore original imports + if (originalImports === undefined) { + delete globalThis.imports; + } else { + globalThis.imports = originalImports; + } + } + }); + + test(`${moduleName} GJS resolver should handle gi:// protocol with version`, async () => { + // Mock the imports object for testing + const originalImports = globalThis.imports; + globalThis.imports = { + gi: { + versions: {}, + Gtk: { init: () => {} } + } + }; + + try { + const gtk = await resolvers.gjs('gi://Gtk?version=4.0'); + expect(gtk).toBeDefined(); + expect(globalThis.imports.gi.versions.Gtk).toBe('4.0'); + } finally { + // Restore original imports + if (originalImports === undefined) { + delete globalThis.imports; + } else { + globalThis.imports = originalImports; + } + } + }); + + test(`${moduleName} GJS resolver should handle built-in GJS modules`, async () => { + // Mock the imports object for testing + const originalImports = globalThis.imports; + globalThis.imports = { + system: { version: '1.0.0' }, + cairo: { Context: function() {} } + }; + + try { + const systemModule = await resolvers.gjs('system'); + expect(systemModule).toBeDefined(); + expect(systemModule.version).toBe('1.0.0'); + + const cairoModule = await resolvers.gjs('cairo'); + expect(cairoModule).toBeDefined(); + expect(typeof cairoModule.Context).toBe('function'); + } finally { + // Restore original imports + if (originalImports === undefined) { + delete globalThis.imports; + } else { + globalThis.imports = originalImports; + } + } + }); + + test(`${moduleName} GJS resolver should throw error for unavailable gi:// modules`, async () => { + // Mock the imports object without the requested library + const originalImports = globalThis.imports; + globalThis.imports = { gi: {} }; + + try { + await expect(resolvers.gjs('gi://NonExistentLib')).rejects.toThrow( + 'GObject introspection library \'NonExistentLib\' not available in this GJS environment.' + ); + } finally { + // Restore original imports + if (originalImports === undefined) { + delete globalThis.imports; + } else { + globalThis.imports = originalImports; + } + } + }); + + test(`${moduleName} GJS resolver should throw error for unavailable built-in modules`, async () => { + // Mock the imports object without the requested built-in + const originalImports = globalThis.imports; + globalThis.imports = {}; + + try { + await expect(resolvers.gjs('cairo')).rejects.toThrow( + 'GJS built-in module \'cairo\' not available.' + ); + } finally { + // Restore original imports + if (originalImports === undefined) { + delete globalThis.imports; + } else { + globalThis.imports = originalImports; + } + } + }); + + test(`${moduleName} makeUse should detect GJS runtime when imports global is present`, async () => { + let mockedImports = false; + if (typeof imports === "undefined") { + globalThis.imports = { gi: {}, system: {} }; + mockedImports = true; + } + try { + const use = await makeUse(); + + // Test by checking which resolver would be used + // We can't directly test the resolver used, but we can test the behavior + // by verifying the resolver produces esm.sh URLs for npm packages + const testModulePath = await resolvers.gjs('lodash@4.17.21'); + expect(testModulePath).toContain('esm.sh'); + } finally { + if (mockedImports) { + delete globalThis.imports; + } + } + }); + + test(`${moduleName} GJS resolver should work with complex package names`, async () => { + const resolvedPath = await resolvers.gjs('@babel/core@7.23.0/lib/index.js'); + expect(resolvedPath).toBe('https://esm.sh/@babel/core@7.23.0/lib/index.js'); + }); + + test(`${moduleName} GJS resolver should handle packages without versions`, async () => { + const resolvedPath = await resolvers.gjs('lodash'); + expect(resolvedPath).toBe('https://esm.sh/lodash@latest'); + }); + + test(`${moduleName} makeUse with explicit gjs resolver`, async () => { + const use = await makeUse({ specifierResolver: 'gjs' }); + + // We can't easily test the full import without network access in CI + // But we can verify the resolver would produce the correct URL + const testUrl = await resolvers.gjs('lodash@4.17.21'); + expect(testUrl).toBe('https://esm.sh/lodash@4.17.21'); + }); +}); \ No newline at end of file diff --git a/use.cjs b/use.cjs index 3917be3..583895b 100644 --- a/use.cjs +++ b/use.cjs @@ -103,49 +103,60 @@ const supportedBuiltins = { // Universal modules 'console': { browser: () => ({ default: console, log: console.log, error: console.error, warn: console.warn, info: console.info }), - node: () => import('node:console').then(m => ({ default: m.Console, ...m })) + node: () => import('node:console').then(m => ({ default: m.Console, ...m })), + gjs: () => ({ default: console, log: console.log, error: console.error, warn: console.warn, info: console.info }) }, 'crypto': { browser: () => ({ default: crypto, subtle: crypto.subtle }), - node: () => import('node:crypto').then(m => ({ default: m, ...m })) + node: () => import('node:crypto').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'url': { browser: () => ({ default: URL, URL, URLSearchParams }), - node: () => import('node:url').then(m => ({ default: m, ...m })) + node: () => import('node:url').then(m => ({ default: m, ...m })), + gjs: () => ({ default: URL, URL, URLSearchParams }) // GJS supports URL and URLSearchParams }, 'performance': { browser: () => ({ default: performance, now: performance.now.bind(performance) }), - node: () => import('node:perf_hooks').then(m => ({ default: m.performance, performance: m.performance, now: m.performance.now.bind(m.performance), ...m })) + node: () => import('node:perf_hooks').then(m => ({ default: m.performance, performance: m.performance, now: m.performance.now.bind(m.performance), ...m })), + gjs: null // Not available in GJS }, // Node.js/Bun only modules 'fs': { browser: null, // Not available in browser - node: () => import('node:fs').then(m => ({ default: m, ...m })) + node: () => import('node:fs').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'path': { browser: null, // Not available in browser - node: () => import('node:path').then(m => ({ default: m, ...m })) + node: () => import('node:path').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'os': { browser: null, // Not available in browser - node: () => import('node:os').then(m => ({ default: m, ...m })) + node: () => import('node:os').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'util': { browser: null, // Not available in browser - node: () => import('node:util').then(m => ({ default: m, ...m })) + node: () => import('node:util').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'events': { browser: null, // Not available in browser - node: () => import('node:events').then(m => ({ default: m.EventEmitter, EventEmitter: m.EventEmitter, ...m })) + node: () => import('node:events').then(m => ({ default: m.EventEmitter, EventEmitter: m.EventEmitter, ...m })), + gjs: null // Not available in GJS }, 'stream': { browser: null, // Not available in browser - node: () => import('node:stream').then(m => ({ default: m.Stream, Stream: m.Stream, ...m })) + node: () => import('node:stream').then(m => ({ default: m.Stream, Stream: m.Stream, ...m })), + gjs: null // Not available in GJS }, 'buffer': { browser: null, // Not available in browser (would need polyfill) - node: () => import('node:buffer').then(m => ({ default: m, Buffer: m.Buffer, ...m })) + node: () => import('node:buffer').then(m => ({ default: m, Buffer: m.Buffer, ...m })), + gjs: null // Not available in GJS }, 'process': { browser: null, // Not available in browser @@ -178,39 +189,48 @@ const supportedBuiltins = { throw new Error(`Failed to resolve 'process' module in Deno environment.`); } return ({ default: process, ...process }); - } + }, + gjs: null // Not available in GJS }, 'child_process': { browser: null, - node: () => import('node:child_process').then(m => ({ default: m, ...m })) + node: () => import('node:child_process').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'http': { browser: null, - node: () => import('node:http').then(m => ({ default: m, ...m })) + node: () => import('node:http').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'https': { browser: null, - node: () => import('node:https').then(m => ({ default: m, ...m })) + node: () => import('node:https').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'net': { browser: null, - node: () => import('node:net').then(m => ({ default: m, ...m })) + node: () => import('node:net').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'dns': { browser: null, - node: () => import('node:dns').then(m => ({ default: m, ...m })) + node: () => import('node:dns').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'zlib': { browser: null, - node: () => import('node:zlib').then(m => ({ default: m, ...m })) + node: () => import('node:zlib').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'querystring': { browser: null, - node: () => import('node:querystring').then(m => ({ default: m, ...m })) + node: () => import('node:querystring').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'assert': { browser: null, - node: () => import('node:assert').then(m => ({ default: m.default || m, ...m })) + node: () => import('node:assert').then(m => ({ default: m.default || m, ...m })), + gjs: null // Not available in GJS } }; @@ -231,7 +251,8 @@ const resolvers = { // Determine environment const isBrowser = typeof window !== 'undefined'; - const environment = isBrowser ? 'browser' : 'node'; + const isGJS = typeof imports !== 'undefined'; + const environment = isBrowser ? 'browser' : (isGJS ? 'gjs' : 'node'); const moduleFactory = builtinConfig[environment]; if (!moduleFactory) { @@ -572,6 +593,45 @@ const resolvers = { const resolvedPath = `https://esm.sh/${packageName}@${version}${modulePath}`; return resolvedPath; }, + gjs: async (moduleSpecifier, pathResolver) => { + // Handle GJS-specific module patterns + + // GJS gi:// protocol for GObject introspection libraries + if (moduleSpecifier.startsWith('gi://')) { + const match = moduleSpecifier.match(/^gi:\/\/([^?]+)(?:\?version=(.+))?$/); + if (match) { + const [, libraryName, version] = match; + + // In GJS, we use the legacy imports object for gi modules + if (typeof imports !== 'undefined' && imports.gi) { + // Set version if specified + if (version && imports.gi.versions) { + imports.gi.versions[libraryName] = version; + } + // Check if the module actually exists + if (imports.gi[libraryName]) { + return imports.gi[libraryName]; + } + } + throw new Error(`GObject introspection library '${libraryName}' not available in this GJS environment.`); + } + } + + // Handle GJS built-in modules (cairo, system, etc.) + const gjsBuiltins = ['cairo', 'system', 'byteArray', 'lang', 'signals', 'tweener', 'gettext', 'format']; + if (gjsBuiltins.includes(moduleSpecifier)) { + if (typeof imports !== 'undefined' && imports[moduleSpecifier]) { + return imports[moduleSpecifier]; + } + throw new Error(`GJS built-in module '${moduleSpecifier}' not available.`); + } + + // For regular npm packages in GJS, fall back to ESM resolution + // This allows GJS to use standard npm packages via CDN + const { packageName, version, modulePath } = parseModuleSpecifier(moduleSpecifier); + const resolvedPath = `https://esm.sh/${packageName}@${version}${modulePath}`; + return resolvedPath; + }, skypack: async (moduleSpecifier, pathResolver) => { const resolvedPath = `https://cdn.skypack.dev/${moduleSpecifier}`; return resolvedPath; @@ -675,6 +735,8 @@ const makeUse = async (options) => { specifierResolver = resolvers[specifierResolver || 'deno']; } else if (typeof Bun !== 'undefined') { specifierResolver = resolvers[specifierResolver || 'bun']; + } else if (typeof imports !== 'undefined') { + specifierResolver = resolvers[specifierResolver || 'gjs']; } else { specifierResolver = resolvers[specifierResolver || 'npm']; } diff --git a/use.mjs b/use.mjs index 250fe81..9b39f00 100644 --- a/use.mjs +++ b/use.mjs @@ -103,49 +103,60 @@ const supportedBuiltins = { // Universal modules 'console': { browser: () => ({ default: console, log: console.log, error: console.error, warn: console.warn, info: console.info }), - node: () => import('node:console').then(m => ({ default: m.Console, ...m })) + node: () => import('node:console').then(m => ({ default: m.Console, ...m })), + gjs: () => ({ default: console, log: console.log, error: console.error, warn: console.warn, info: console.info }) }, 'crypto': { browser: () => ({ default: crypto, subtle: crypto.subtle }), - node: () => import('node:crypto').then(m => ({ default: m, ...m })) + node: () => import('node:crypto').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'url': { browser: () => ({ default: URL, URL, URLSearchParams }), - node: () => import('node:url').then(m => ({ default: m, ...m })) + node: () => import('node:url').then(m => ({ default: m, ...m })), + gjs: () => ({ default: URL, URL, URLSearchParams }) // GJS supports URL and URLSearchParams }, 'performance': { browser: () => ({ default: performance, now: performance.now.bind(performance) }), - node: () => import('node:perf_hooks').then(m => ({ default: m.performance, performance: m.performance, now: m.performance.now.bind(m.performance), ...m })) + node: () => import('node:perf_hooks').then(m => ({ default: m.performance, performance: m.performance, now: m.performance.now.bind(m.performance), ...m })), + gjs: null // Not available in GJS }, // Node.js/Bun only modules 'fs': { browser: null, // Not available in browser - node: () => import('node:fs').then(m => ({ default: m, ...m })) + node: () => import('node:fs').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'path': { browser: null, // Not available in browser - node: () => import('node:path').then(m => ({ default: m, ...m })) + node: () => import('node:path').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'os': { browser: null, // Not available in browser - node: () => import('node:os').then(m => ({ default: m, ...m })) + node: () => import('node:os').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'util': { browser: null, // Not available in browser - node: () => import('node:util').then(m => ({ default: m, ...m })) + node: () => import('node:util').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'events': { browser: null, // Not available in browser - node: () => import('node:events').then(m => ({ default: m.EventEmitter, EventEmitter: m.EventEmitter, ...m })) + node: () => import('node:events').then(m => ({ default: m.EventEmitter, EventEmitter: m.EventEmitter, ...m })), + gjs: null // Not available in GJS }, 'stream': { browser: null, // Not available in browser - node: () => import('node:stream').then(m => ({ default: m.Stream, Stream: m.Stream, ...m })) + node: () => import('node:stream').then(m => ({ default: m.Stream, Stream: m.Stream, ...m })), + gjs: null // Not available in GJS }, 'buffer': { browser: null, // Not available in browser (would need polyfill) - node: () => import('node:buffer').then(m => ({ default: m, Buffer: m.Buffer, ...m })) + node: () => import('node:buffer').then(m => ({ default: m, Buffer: m.Buffer, ...m })), + gjs: null // Not available in GJS }, 'process': { browser: null, // Not available in browser @@ -178,39 +189,48 @@ const supportedBuiltins = { throw new Error(`Failed to resolve 'process' module in Deno environment.`); } return ({ default: process, ...process }); - } + }, + gjs: null // Not available in GJS }, 'child_process': { browser: null, - node: () => import('node:child_process').then(m => ({ default: m, ...m })) + node: () => import('node:child_process').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'http': { browser: null, - node: () => import('node:http').then(m => ({ default: m, ...m })) + node: () => import('node:http').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'https': { browser: null, - node: () => import('node:https').then(m => ({ default: m, ...m })) + node: () => import('node:https').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'net': { browser: null, - node: () => import('node:net').then(m => ({ default: m, ...m })) + node: () => import('node:net').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'dns': { browser: null, - node: () => import('node:dns').then(m => ({ default: m, ...m })) + node: () => import('node:dns').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'zlib': { browser: null, - node: () => import('node:zlib').then(m => ({ default: m, ...m })) + node: () => import('node:zlib').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'querystring': { browser: null, - node: () => import('node:querystring').then(m => ({ default: m, ...m })) + node: () => import('node:querystring').then(m => ({ default: m, ...m })), + gjs: null // Not available in GJS }, 'assert': { browser: null, - node: () => import('node:assert').then(m => ({ default: m.default || m, ...m })) + node: () => import('node:assert').then(m => ({ default: m.default || m, ...m })), + gjs: null // Not available in GJS } }; @@ -231,7 +251,8 @@ export const resolvers = { // Determine environment const isBrowser = typeof window !== 'undefined'; - const environment = isBrowser ? 'browser' : 'node'; + const isGJS = typeof imports !== 'undefined'; + const environment = isBrowser ? 'browser' : (isGJS ? 'gjs' : 'node'); const moduleFactory = builtinConfig[environment]; if (!moduleFactory) { @@ -572,6 +593,45 @@ export const resolvers = { const resolvedPath = `https://esm.sh/${packageName}@${version}${modulePath}`; return resolvedPath; }, + gjs: async (moduleSpecifier, pathResolver) => { + // Handle GJS-specific module patterns + + // GJS gi:// protocol for GObject introspection libraries + if (moduleSpecifier.startsWith('gi://')) { + const match = moduleSpecifier.match(/^gi:\/\/([^?]+)(?:\?version=(.+))?$/); + if (match) { + const [, libraryName, version] = match; + + // In GJS, we use the legacy imports object for gi modules + if (typeof imports !== 'undefined' && imports.gi) { + // Set version if specified + if (version && imports.gi.versions) { + imports.gi.versions[libraryName] = version; + } + // Check if the module actually exists + if (imports.gi[libraryName]) { + return imports.gi[libraryName]; + } + } + throw new Error(`GObject introspection library '${libraryName}' not available in this GJS environment.`); + } + } + + // Handle GJS built-in modules (cairo, system, etc.) + const gjsBuiltins = ['cairo', 'system', 'byteArray', 'lang', 'signals', 'tweener', 'gettext', 'format']; + if (gjsBuiltins.includes(moduleSpecifier)) { + if (typeof imports !== 'undefined' && imports[moduleSpecifier]) { + return imports[moduleSpecifier]; + } + throw new Error(`GJS built-in module '${moduleSpecifier}' not available.`); + } + + // For regular npm packages in GJS, fall back to ESM resolution + // This allows GJS to use standard npm packages via CDN + const { packageName, version, modulePath } = parseModuleSpecifier(moduleSpecifier); + const resolvedPath = `https://esm.sh/${packageName}@${version}${modulePath}`; + return resolvedPath; + }, skypack: async (moduleSpecifier, pathResolver) => { const resolvedPath = `https://cdn.skypack.dev/${moduleSpecifier}`; return resolvedPath; @@ -678,6 +738,8 @@ export const makeUse = async (options) => { specifierResolver = resolvers[specifierResolver || 'deno']; } else if (typeof Bun !== 'undefined') { specifierResolver = resolvers[specifierResolver || 'bun']; + } else if (typeof imports !== 'undefined') { + specifierResolver = resolvers[specifierResolver || 'gjs']; } else { specifierResolver = resolvers[specifierResolver || 'npm']; } diff --git a/yarn.lock b/yarn.lock index 7c44d21..f286c44 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1915,11 +1915,6 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.3.2: - version "2.3.3" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - function-bind@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"