diff --git a/doc/importing.md b/doc/importing.md index 0b00f6a5..3247a9a8 100644 --- a/doc/importing.md +++ b/doc/importing.md @@ -38,7 +38,7 @@ package by name: import gi, { registerClass } from 'node-gtk' ``` -The `gi:` hooks require **Node ≥ 20.6** (for `module.register`). +The `gi:` hooks require **Node ≥ 22.15** (for `module.registerHooks`). ### The loop integration starts automatically diff --git a/lib/esm/hooks.mjs b/lib/esm/hooks.mjs deleted file mode 100644 index f8ddbe77..00000000 --- a/lib/esm/hooks.mjs +++ /dev/null @@ -1,49 +0,0 @@ -/* - * hooks.mjs — Node.js ESM module-customization hooks for the `gi:` scheme. - * - * Enables `import Gtk from 'gi:Gtk-4.0'`, where the default export is the - * namespace object returned by node-gtk's `gi.require('Gtk', '4.0')`. Members are - * read off that object: `const { Box, Label } = Gtk`. - * - * Install them with `node --import node-gtk/register app.mjs` (see register.mjs). - * - * The hooks run on a separate loader thread, so they do NO native work: `load` - * only emits a tiny synthetic ES module whose body calls `gi.require` on the - * main thread when the module is evaluated. Requires Node >= 20.6 (module.register). - */ - -const PREFIX = 'gi:' - -/* Absolute file:// URL to lib/index.js, embedded into the generated source. The - * synthetic module's parent URL is the schemeless `gi:` URL, which has no - * filesystem base, so a bare `import 'node-gtk'` could not be resolved from it — - * the absolute URL sidesteps resolution entirely. */ -const INDEX_URL = new URL('../index.js', import.meta.url).href - -export async function resolve(specifier, context, nextResolve) { - if (specifier.startsWith(PREFIX)) - return { url: specifier, shortCircuit: true } - return nextResolve(specifier, context) -} - -export async function load(url, context, nextLoad) { - if (!url.startsWith(PREFIX)) - return nextLoad(url, context) - - // `gi:Gtk-4.0` -> ('Gtk', '4.0'); `gi:Adw-1` -> ('Adw', '1'); `gi:cairo` -> ('cairo', null). - // Split on the first '-' only: GI namespace names never contain '-', versions may. - const spec = url.slice(PREFIX.length) - const dash = spec.indexOf('-') - const name = dash === -1 ? spec : spec.slice(0, dash) - const version = dash === -1 ? null : spec.slice(dash + 1) - - const call = version === null - ? `gi.require(${JSON.stringify(name)})` - : `gi.require(${JSON.stringify(name)}, ${JSON.stringify(version)})` - - const source = - `import gi from ${JSON.stringify(INDEX_URL)};\n` + - `export default ${call};\n` - - return { format: 'module', shortCircuit: true, source } -} diff --git a/lib/esm/register.mjs b/lib/esm/register.mjs index 2ff5b0c9..51a4b0f9 100644 --- a/lib/esm/register.mjs +++ b/lib/esm/register.mjs @@ -4,14 +4,58 @@ * Usage: node --import node-gtk/register app.mjs * * Then, in app.mjs: - * import Gtk from 'gi:Gtk-4.0' + * import Gtk from 'gi:Gtk-4.0' // `gi:Name-Version`, or `gi:Name` for the latest * const { Box, Label } = Gtk * + * `import Gtk from 'gi:Gtk-4.0'` is equivalent to `gi.require('Gtk', '4.0')`: the + * default export is the namespace object, so read members off it. + * + * The hooks are synchronous and run in-thread (module.registerHooks), so they must + * not return promises. `load` does no native work itself: it emits a tiny synthetic + * ES module whose body calls `gi.require` when that module is evaluated. Requires + * Node >= 22.15 (module.registerHooks). + * * Note: hooks only affect imports evaluated *after* registration. To use a static * `import ... from 'gi:...'` in your entry module, register via the `--import` * flag above (not a programmatic `import 'node-gtk/register'` in that same file). */ -import { register } from 'node:module' +import { registerHooks } from 'node:module' + +const PREFIX = 'gi:' + +/* Absolute file:// URL to lib/index.js, embedded into the generated source. The + * synthetic module's parent URL is the schemeless `gi:` URL, which has no + * filesystem base, so a bare `import 'node-gtk'` could not be resolved from it — + * the absolute URL sidesteps resolution entirely. */ +const INDEX_URL = new URL('../index.js', import.meta.url).href + +function resolve(specifier, context, nextResolve) { + if (specifier.startsWith(PREFIX)) + return { url: specifier, shortCircuit: true } + return nextResolve(specifier, context) +} + +function load(url, context, nextLoad) { + if (!url.startsWith(PREFIX)) + return nextLoad(url, context) + + // `gi:Gtk-4.0` -> ('Gtk', '4.0'); `gi:Adw-1` -> ('Adw', '1'); `gi:cairo` -> ('cairo', null). + // Split on the first '-' only: GI namespace names never contain '-', versions may. + const spec = url.slice(PREFIX.length) + const dash = spec.indexOf('-') + const name = dash === -1 ? spec : spec.slice(0, dash) + const version = dash === -1 ? null : spec.slice(dash + 1) + + const call = version === null + ? `gi.require(${JSON.stringify(name)})` + : `gi.require(${JSON.stringify(name)}, ${JSON.stringify(version)})` + + const source = + `import gi from ${JSON.stringify(INDEX_URL)};\n` + + `export default ${call};\n` + + return { format: 'module', shortCircuit: true, source } +} -register(new URL('./hooks.mjs', import.meta.url)) +registerHooks({ resolve, load }) diff --git a/package.json b/package.json index 9433ef72..1f2f6421 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,6 @@ "require": "./lib/index.js" }, "./register": "./lib/esm/register.mjs", - "./hooks": "./lib/esm/hooks.mjs", "./styles": { "types": "./lib/styles.d.ts", "default": "./lib/styles.js"