@gjsify/rolldown-native 0.41.0 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @gjsify/rolldown-native
2
2
 
3
- A native Rust cdylib + Vala/GObject bridge that wraps the Rust `rolldown` bundler and exposes it to GJS via `gi://`. This is the default bundler engine used by `gjsify build` under GJS — npm's `rolldown` is an N-API addon that cannot load in GJS, so this bridge is how gjsify bundles without a Node runtime. Includes a complete plugin bridge (`bundleWithPlugins`) for load, transform, resolveId, and render-chunk hooks. Ships prebuilt `.so` + `.typelib` for **Linux**; the source is now platform-neutral and cross-compiles for macOS, but no macOS prebuild is published yet — see [Platform coverage](#platform-coverage).
3
+ A native Rust cdylib + Vala/GObject bridge that wraps the Rust `rolldown` bundler and exposes it to GJS via `gi://`. This is the default bundler engine used by `gjsify build` under GJS — npm's `rolldown` does not run under GJS — its JS entry evaluates `createRequire(import.meta.url)` at module scope and then synchronously requires `node:fs` / `node:child_process` for platform detection, which GJS refuses before any `.node` is ever opened — so this bridge is how gjsify bundles without a Node runtime. Includes a complete plugin bridge (`bundleWithPlugins`) for load, transform, resolveId, and render-chunk hooks. Ships prebuilt `.so` + `.typelib` for **Linux**; the source is now platform-neutral and cross-compiles for macOS, but no macOS prebuild is published yet — see [Platform coverage](#platform-coverage).
4
4
 
5
5
  Part of the [gjsify](https://github.com/gjsify/gjsify) project — Node.js and Web APIs for GJS (GNOME JavaScript).
6
6
 
package/lib/esm/index.js CHANGED
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import{bundleWithPlugins as e}from"./plugins.js";let t=null,n=!1,r=null;function loadNativeRolldown(){if(n)return t;n=!0;try{let e=globalThis.imports?.gi;if(!e)return r=Error(`imports.gi not available — not running under GJS?`),null;let n=e.GjsifyRolldown;return n?(t=n,t):(r=Error(`GjsifyRolldown typelib not found on GI_TYPELIB_PATH`),null)}catch(e){return r=e instanceof Error?e:Error(String(e)),t=null,null}}function hasNativeRolldown(){return loadNativeRolldown()!==null}function getLoadError(){return r}function getGLib(){let e=globalThis.imports?.gi;if(!e||!e.GLib)throw Error(`@gjsify/rolldown-native: GLib not available (not running under GJS?)`);return e.GLib}function bundle(e){let t=loadNativeRolldown();if(!t)throw Error(`@gjsify/rolldown-native: prebuild not available (${r?.message??`unknown`})`);let n=JSON.stringify(e),i=getGLib().Bytes.new(new TextEncoder().encode(n)),a=new t.Bundler().bundle(i).get_data()??new Uint8Array;return JSON.parse(new TextDecoder().decode(a))}export{bundle,e as bundleWithPlugins,getLoadError,hasNativeRolldown,loadNativeRolldown};
1
+ import"./_virtual/_rolldown/runtime.js";import{bundleWithPlugins as e,isResolveMiss as t}from"./plugins.js";let n=null,r=!1,i=null;function loadNativeRolldown(){if(r)return n;r=!0;try{let e=globalThis.imports?.gi;if(!e)return i=Error(`imports.gi not available — not running under GJS?`),null;let t=e.GjsifyRolldown;return t?(n=t,n):(i=Error(`GjsifyRolldown typelib not found on GI_TYPELIB_PATH`),null)}catch(e){return i=e instanceof Error?e:Error(String(e)),n=null,null}}function hasNativeRolldown(){return loadNativeRolldown()!==null}function getLoadError(){return i}function getGLib(){let e=globalThis.imports?.gi;if(!e||!e.GLib)throw Error(`@gjsify/rolldown-native: GLib not available (not running under GJS?)`);return e.GLib}function bundle(e){let t=loadNativeRolldown();if(!t)throw Error(`@gjsify/rolldown-native: prebuild not available (${i?.message??`unknown`})`);let n=JSON.stringify(e),r=getGLib().Bytes.new(new TextEncoder().encode(n)),a=new t.Bundler().bundle(r).get_data()??new Uint8Array;return JSON.parse(new TextDecoder().decode(a))}export{bundle,e as bundleWithPlugins,getLoadError,hasNativeRolldown,t as isResolveMiss,loadNativeRolldown};
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import{loadNativeRolldown as e}from"./index.js";function getGLib(){let e=globalThis.imports?.gi;if(!e||!e.GLib)throw Error(`@gjsify/rolldown-native: GLib not available (not running under GJS?)`);return e.GLib}const enc=e=>getGLib().Bytes.new(new TextEncoder().encode(e)),dec=e=>new TextDecoder().decode(e.get_data()??new Uint8Array),t=[`load`,`transform`,`resolveId`,`renderChunk`,`banner`,`footer`,`intro`,`outro`,`buildStart`,`buildEnd`,`generateBundle`,`writeBundle`,`closeBundle`];function pluginMeta(e){let n=t.filter(t=>typeof e[t]==`function`),r={name:e.name,hooks:n};return e.idFilter&&(r.idFilter=e.idFilter),r}const n=new Set;function bundleWithPlugins(t,r){let i=e();if(!i)return Promise.reject(Error(`@gjsify/rolldown-native: prebuild not available`));let a=new i.BundlerSession;n.add(a);let o=new Map;a.connect(`context_response`,(e,t,n)=>{let r=o.get(t);if(!r)return;o.delete(t);let i;try{i=JSON.parse(dec(n))}catch(e){r.reject(e instanceof Error?e:Error(String(e)));return}i.error?r.reject(Error(i.error)):i.id?r.resolve({id:i.id,external:i.external??!1}):r.resolve(null)});function makeContext(e){return{resolve(t,n,r){return new Promise((i,s)=>{let c=a.context_resolve(e,enc(JSON.stringify({specifier:t,importer:n,skipSelf:r?.skipSelf??!0,isEntry:r?.isEntry??!1})));if(c===0){s(Error(`@gjsify/rolldown-native: ctx.resolve('${t}') failed — parent req_id ${e} unknown`));return}o.set(c,{resolve:i,reject:s})})},warn(e){a.context_warn(enc(e))},error(e){throw Error(e)}}}a.connect(`hook_requested`,(e,t,n,r,i)=>{dispatchHook(t,n,r,i)});async function dispatchHook(e,t,n,i){let o;try{o=JSON.parse(dec(i))}catch(e){respondError(t,e);return}let s=r[n];if(!s){respondError(t,Error(`@gjsify/rolldown-native: unknown plugin index ${n}`));return}let c=makeContext(t);try{if(e===`transform`&&s.transform){let e=o,n=a.take_request_payload(t);if(n===null)throw Error(`@gjsify/rolldown-native: transform request for ${e.id} missing payload bytes`);let r=dec(n);respondTransform(t,await s.transform.call(c,r,e.id,e.moduleType));return}respondOk(t,await runHook(s,e,o,c))}catch(e){respondError(t,e)}}function respondOk(e,t){let n=t==null?{kind:`skip`}:{kind:`ok`,value:t};a.respond(e,enc(JSON.stringify(n)))}function respondTransform(e,t){if(t==null){a.respond(e,enc(JSON.stringify({kind:`skip`})));return}let n,r;if(typeof t==`string`)n=t;else if(typeof t==`object`){let e=t;n=e.code,r=e.moduleType}if(typeof n!=`string`){a.respond(e,enc(JSON.stringify({kind:`skip`})));return}let i=getGLib().Bytes.new(new TextEncoder().encode(n));if(!a.set_response_payload(e,i)){respondError(e,Error(`@gjsify/rolldown-native: failed to stash transform response payload for reqId ${e}`));return}a.respond(e,enc(JSON.stringify({kind:`ok`,value:r===void 0?{hasCodeBytes:!0}:{hasCodeBytes:!0,moduleType:r}})))}function respondError(e,t){let n=t instanceof Error?t:Error(String(t));a.respond(e,enc(JSON.stringify({kind:`error`,message:n.message,stack:n.stack})))}let closeSession=()=>{n.delete(a);let e=a;try{e.close?.()}catch{}};return new Promise((e,n)=>{a.connect(`completed`,(t,r)=>{try{e(JSON.parse(dec(r)))}catch(e){n(e instanceof Error?e:Error(String(e)))}}),a.connect(`error_occurred`,(e,t)=>{n(Error(t))});try{a.start(enc(JSON.stringify({options:t,plugins:r.map(pluginMeta)})))}catch(e){n(e instanceof Error?e:Error(String(e)))}}).finally(closeSession)}async function runHook(e,t,n,r){switch(t){case`load`:return e.load?normalizeLoadResult(await e.load.call(r,n.id)):null;case`transform`:return null;case`resolveId`:{if(!e.resolveId)return null;let t=n;return normalizeResolveIdResult(await e.resolveId.call(r,t.specifier,t.importer,{isEntry:t.isEntry}))}case`renderChunk`:{if(!e.renderChunk)return null;let t=n;return normalizeRenderChunkResult(await e.renderChunk.call(r,t.code,{fileName:t.fileName,name:t.name,isEntry:t.isEntry}))}case`banner`:case`footer`:case`intro`:case`outro`:{let i=e[t];if(!i)return null;let a=n;return normalizeAddonResult(await i.call(r,{fileName:a.fileName,name:a.name,isEntry:a.isEntry}))}case`buildStart`:case`buildEnd`:case`generateBundle`:case`writeBundle`:case`closeBundle`:{let n=e[t];return n&&await n.call(r),null}default:throw Error(`@gjsify/rolldown-native: unknown hook '${t}'`)}}function normalizeLoadResult(e){return e==null?null:typeof e==`string`?{code:e}:{code:e.code,moduleType:e.moduleType}}function normalizeResolveIdResult(e){return e==null?null:typeof e==`string`?{id:e}:{id:e.id,external:e.external}}function normalizeRenderChunkResult(e){return e==null?null:typeof e==`string`?{code:e}:{code:e.code}}function normalizeAddonResult(e){return e==null?null:typeof e==`string`?e:e.text}export{bundleWithPlugins};
1
+ import"./_virtual/_rolldown/runtime.js";import{loadNativeRolldown as e}from"./index.js";function getGLib(){let e=globalThis.imports?.gi;if(!e||!e.GLib)throw Error(`@gjsify/rolldown-native: GLib not available (not running under GJS?)`);return e.GLib}const enc=e=>getGLib().Bytes.new(new TextEncoder().encode(e)),dec=e=>new TextDecoder().decode(e.get_data()??new Uint8Array),t=[`load`,`transform`,`resolveId`,`renderChunk`,`banner`,`footer`,`intro`,`outro`,`buildStart`,`buildEnd`,`generateBundle`,`writeBundle`,`closeBundle`];function pluginMeta(e){let n=t.filter(t=>typeof e[t]==`function`),r={name:e.name,hooks:n};return e.idFilter&&(r.idFilter=e.idFilter),r}const n=new Set;function isResolveMiss(e){return e.startsWith(`NotFound(`)}function bundleWithPlugins(t,r){let i=e();if(!i)return Promise.reject(Error(`@gjsify/rolldown-native: prebuild not available`));let a=new i.BundlerSession;n.add(a);let o=new Map;a.connect(`context_response`,(e,t,n)=>{let r=o.get(t);if(!r)return;o.delete(t);let i;try{i=JSON.parse(dec(n))}catch(e){r.reject(e instanceof Error?e:Error(String(e)));return}i.error&&isResolveMiss(i.error)?r.resolve(null):i.error?r.reject(Error(i.error)):i.id?r.resolve({id:i.id,external:i.external??!1}):r.resolve(null)});function makeContext(e){return{resolve(t,n,r){return new Promise((i,s)=>{let c=a.context_resolve(e,enc(JSON.stringify({specifier:t,importer:n,skipSelf:r?.skipSelf??!0,isEntry:r?.isEntry??!1})));if(c===0){s(Error(`@gjsify/rolldown-native: ctx.resolve('${t}') failed — parent req_id ${e} unknown`));return}o.set(c,{resolve:i,reject:s})})},warn(e){a.context_warn(enc(e))},error(e){throw Error(e)}}}a.connect(`hook_requested`,(e,t,n,r,i)=>{dispatchHook(t,n,r,i)});async function dispatchHook(e,t,n,i){let o;try{o=JSON.parse(dec(i))}catch(e){respondError(t,e);return}let s=r[n];if(!s){respondError(t,Error(`@gjsify/rolldown-native: unknown plugin index ${n}`));return}let c=makeContext(t);try{if(e===`transform`&&s.transform){let e=o,n=a.take_request_payload(t);if(n===null)throw Error(`@gjsify/rolldown-native: transform request for ${e.id} missing payload bytes`);let r=dec(n);respondTransform(t,await s.transform.call(c,r,e.id,e.moduleType));return}respondOk(t,await runHook(s,e,o,c))}catch(e){respondError(t,e)}}function respondOk(e,t){let n=t==null?{kind:`skip`}:{kind:`ok`,value:t};a.respond(e,enc(JSON.stringify(n)))}function respondTransform(e,t){if(t==null){a.respond(e,enc(JSON.stringify({kind:`skip`})));return}let n,r;if(typeof t==`string`)n=t;else if(typeof t==`object`){let e=t;n=e.code,r=e.moduleType}if(typeof n!=`string`){a.respond(e,enc(JSON.stringify({kind:`skip`})));return}let i=getGLib().Bytes.new(new TextEncoder().encode(n));if(!a.set_response_payload(e,i)){respondError(e,Error(`@gjsify/rolldown-native: failed to stash transform response payload for reqId ${e}`));return}a.respond(e,enc(JSON.stringify({kind:`ok`,value:r===void 0?{hasCodeBytes:!0}:{hasCodeBytes:!0,moduleType:r}})))}function respondError(e,t){let n=t instanceof Error?t:Error(String(t));a.respond(e,enc(JSON.stringify({kind:`error`,message:n.message,stack:n.stack})))}let closeSession=()=>{n.delete(a);let e=a;try{e.close?.()}catch{}};return new Promise((e,n)=>{a.connect(`completed`,(t,r)=>{try{e(JSON.parse(dec(r)))}catch(e){n(e instanceof Error?e:Error(String(e)))}}),a.connect(`error_occurred`,(e,t)=>{n(Error(t))});try{a.start(enc(JSON.stringify({options:t,plugins:r.map(pluginMeta)})))}catch(e){n(e instanceof Error?e:Error(String(e)))}}).finally(closeSession)}async function runHook(e,t,n,r){switch(t){case`load`:return e.load?normalizeLoadResult(await e.load.call(r,n.id)):null;case`transform`:return null;case`resolveId`:{if(!e.resolveId)return null;let t=n;return normalizeResolveIdResult(await e.resolveId.call(r,t.specifier,t.importer,{isEntry:t.isEntry}))}case`renderChunk`:{if(!e.renderChunk)return null;let t=n;return normalizeRenderChunkResult(await e.renderChunk.call(r,t.code,{fileName:t.fileName,name:t.name,isEntry:t.isEntry}))}case`banner`:case`footer`:case`intro`:case`outro`:{let i=e[t];if(!i)return null;let a=n;return normalizeAddonResult(await i.call(r,{fileName:a.fileName,name:a.name,isEntry:a.isEntry}))}case`buildStart`:case`buildEnd`:case`generateBundle`:case`writeBundle`:case`closeBundle`:{let n=e[t];return n&&await n.call(r),null}default:throw Error(`@gjsify/rolldown-native: unknown hook '${t}'`)}}function normalizeLoadResult(e){return e==null?null:typeof e==`string`?{code:e}:{code:e.code,moduleType:e.moduleType}}function normalizeResolveIdResult(e){return e==null?null:typeof e==`string`?{id:e}:{id:e.id,external:e.external}}function normalizeRenderChunkResult(e){return e==null?null:typeof e==`string`?{code:e}:{code:e.code}}function normalizeAddonResult(e){return e==null?null:typeof e==`string`?e:e.text}export{bundleWithPlugins,isResolveMiss};
@@ -50,7 +50,7 @@ export interface BundleResult {
50
50
  warnings: string[];
51
51
  output: BundleOutputItem[];
52
52
  }
53
- export { bundleWithPlugins, type NativePlugin, type NativePluginContext, type PluginIdFilter, type LoadHookHandler, type TransformHookHandler, type ResolveIdHookHandler, type RenderChunkHookHandler, type AddonHookHandler, type LifecycleHookHandler, } from './plugins.js';
53
+ export { bundleWithPlugins, isResolveMiss, type NativePlugin, type NativePluginContext, type PluginIdFilter, type LoadHookHandler, type TransformHookHandler, type ResolveIdHookHandler, type RenderChunkHookHandler, type AddonHookHandler, type LifecycleHookHandler, } from './plugins.js';
54
54
  /**
55
55
  * Convenience facade: serialize options to JSON, call the native
56
56
  * bundler, parse the JSON output. Throws if the prebuild is unavailable
@@ -97,4 +97,27 @@ export interface NativePlugin {
97
97
  * Throws if the prebuild isn't loadable. Rejects the returned Promise
98
98
  * if the underlying build fails.
99
99
  */
100
+ /**
101
+ * Does a `context_resolve` error string mean "nothing resolved" rather than "the
102
+ * resolve itself failed"?
103
+ *
104
+ * THE TWO ENGINES HAVE TO AGREE, and this is where ours was wrong. `ctx.resolve`
105
+ * is declared (`NativePluginContext.resolve`) to return `null` when the specifier
106
+ * doesn't resolve, because that is what npm `rolldown` does and what every caller
107
+ * in `@gjsify/rolldown-plugin-gjsify` assumes — nine of them, `alias.ts`,
108
+ * `platform-resolve.ts`, `entry-wrapper.ts` and five in `napi-node-addon.ts`,
109
+ * none with a catch, one of them a bare presence probe
110
+ * (`Boolean(await ctx.resolve(…))`). The Rust side maps EVERY `ResolveError`
111
+ * through `format!("{resolve_err:?}")` into `error`, so a plain miss arrived as a
112
+ * REJECTION and those call sites threw instead of taking their not-found branch.
113
+ * Measured on gjs 1.88.1 with the real prebuild: an unresolvable bare specifier
114
+ * rejected with `NotFound("@gjsify/definitely-not-there")`.
115
+ *
116
+ * Only `NotFound` is a miss. `InvalidPackageConfig`, `PackagePathNotExported`,
117
+ * `IOError`, a `BuildError` from another plugin's hook, and the two bridge-internal
118
+ * faults below (malformed payload, unknown parent req_id) are all real failures and
119
+ * keep rejecting — turning those into `null` is how a bridge bug ends up reported
120
+ * as the user's missing dependency.
121
+ */
122
+ export declare function isResolveMiss(error: string): boolean;
100
123
  export declare function bundleWithPlugins(options: BundleOptions, plugins: NativePlugin[]): Promise<BundleResult>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/rolldown-native",
3
- "version": "0.41.0",
3
+ "version": "0.43.0",
4
4
  "description": "Phase D-2 POC: Vala/GObject + Rust cdylib bridge to rolldown for GJS. Wraps `rolldown::Bundler::generate()` (async, tokio) in a per-call current-thread runtime so JS sees a sync `bundle()` call. JSON-encoded BundlerOptions in, JSON-encoded BundleOutput out — the boundary stays small even though rolldown's option surface is enormous. POC scope: no JS plugins (Phase B), no watch/HMR, no incremental builds. Companion to `@gjsify/lightningcss-native`; together they remove the last two Rust-crate runtime blockers from the gjsify build pipeline (Phase D-3 unblock).",
5
5
  "type": "module",
6
6
  "main": "lib/esm/index.js",
@@ -58,7 +58,7 @@
58
58
  "@girs/gobject-2.0": "^4.1.0"
59
59
  },
60
60
  "devDependencies": {
61
- "@gjsify/cli": "^0.41.0",
61
+ "@gjsify/cli": "^0.43.0",
62
62
  "@types/node": "^25.9.2",
63
63
  "typescript": "^6.0.3"
64
64
  },
@@ -74,9 +74,9 @@
74
74
  },
75
75
  "homepage": "https://github.com/gjsify/gjsify/tree/main/packages/infra/rolldown-native#readme",
76
76
  "optionalDependencies": {
77
- "@gjsify/rolldown-native-darwin-arm64": "0.41.0",
78
- "@gjsify/rolldown-native-darwin-x64": "0.41.0",
79
- "@gjsify/rolldown-native-linux-arm64": "0.41.0",
80
- "@gjsify/rolldown-native-linux-x64": "0.41.0"
77
+ "@gjsify/rolldown-native-darwin-arm64": "0.43.0",
78
+ "@gjsify/rolldown-native-darwin-x64": "0.43.0",
79
+ "@gjsify/rolldown-native-linux-arm64": "0.43.0",
80
+ "@gjsify/rolldown-native-linux-x64": "0.43.0"
81
81
  }
82
82
  }
@@ -651,11 +651,28 @@ pub extern "C" fn gjsify_rolldown_session_context_resolve(
651
651
  external: None,
652
652
  error: Some(format!("{resolve_err:?}")),
653
653
  },
654
+ // `{e:#}`, NOT `{e}`. This arm carries an `anyhow::Error`, whose
655
+ // OUTERMOST message is never the interesting part — the cause chain
656
+ // underneath is, and that is where a user plugin's own error text
657
+ // lives. `{e}` printed the outer line alone, so a plugin that threw
658
+ // during a NESTED `this.resolve()` reached JS as a bare
659
+ // "plugin `gjsify-alias` threw an error" with the diagnostic that
660
+ // names the specifier, the substitution target and the importer
661
+ // discarded. The `Ok(Err(resolve_err))` arm above has always printed
662
+ // its full form; the two simply disagreed, and this is the arm a
663
+ // plugin failure takes.
664
+ //
665
+ // The alternate form reaches `isResolveMiss` in `../../ts/plugins.ts`,
666
+ // which folds a `context_resolve` error into "nothing there" only when
667
+ // it starts with `NotFound(`. A chain from THIS arm opens with the
668
+ // outer bundler/plugin message, never that — pinned by
669
+ // `@gjsify/cli`'s `bundler-pick.spec.ts`, so the two cannot drift into
670
+ // reporting a real plugin failure as the user's missing dependency.
654
671
  Err(e) => ContextResolveResponse {
655
672
  child_id,
656
673
  id: None,
657
674
  external: None,
658
- error: Some(format!("{e}")),
675
+ error: Some(format!("{e:#}")),
659
676
  },
660
677
  };
661
678
  let _ = shared.context_response_tx.send(resp);