@gjsify/rolldown-plugin-gjsify 0.17.0 → 0.19.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/lib/app/index.d.ts +1 -1
- package/lib/app/index.js +1 -1
- package/lib/app/node.d.ts +22 -0
- package/lib/app/node.js +87 -12
- package/lib/plugins/gjs-gi-node.d.ts +13 -6
- package/lib/plugins/gjs-gi-node.js +13 -6
- package/lib/utils/runtime.d.ts +60 -7
- package/lib/utils/runtime.js +82 -8
- package/lib/utils/scan-globals.d.ts +22 -1
- package/lib/utils/scan-globals.js +43 -5
- package/package.json +7 -7
package/lib/app/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { setupForGjs, isRegisterSubpath, createGjsExternalsPredicate } from './gjs.js';
|
|
2
2
|
export type { GjsBuildConfig, GjsFactoryInput } from './gjs.js';
|
|
3
|
-
export { setupForNode } from './node.js';
|
|
3
|
+
export { setupForNode, enableGjsRegistersForNode } from './node.js';
|
|
4
4
|
export type { NodeBuildConfig, NodeFactoryInput } from './node.js';
|
|
5
5
|
export { setupForBrowser } from './browser.js';
|
|
6
6
|
export type { BrowserBuildConfig, BrowserFactoryInput } from './browser.js';
|
package/lib/app/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { setupForGjs, isRegisterSubpath, createGjsExternalsPredicate } from './gjs.js';
|
|
2
|
-
export { setupForNode } from './node.js';
|
|
2
|
+
export { setupForNode, enableGjsRegistersForNode } from './node.js';
|
|
3
3
|
export { setupForBrowser } from './browser.js';
|
|
4
4
|
export { setupForNativescript } from './nativescript.js';
|
package/lib/app/node.d.ts
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import type { RolldownOptions, RolldownPluginOption } from 'rolldown';
|
|
2
2
|
import type { PluginOptions } from '../types/plugin-options.js';
|
|
3
|
+
/**
|
|
4
|
+
* Reverse-bridge register routing: a node build with an EXPLICIT `--globals`
|
|
5
|
+
* register-inject stub (`pluginOptions.autoGlobalsInject`, set by the CLI for
|
|
6
|
+
* `--app node` when the user names globals — e.g. `--globals auto,dom`) is a
|
|
7
|
+
* genuine GJS source that needs the REAL `@gjsify/*` register bodies (document,
|
|
8
|
+
* HTMLCanvasElement, matchMedia, …) running over `@gjsify/node-gi`, not the
|
|
9
|
+
* default `@gjsify/empty` stubs that keep cross-platform node bundles loadable
|
|
10
|
+
* on plain Node.
|
|
11
|
+
*
|
|
12
|
+
* Two adjustments over the standard node alias map:
|
|
13
|
+
* 1. every `<pkg>/register…` → `@gjsify/empty` entry is DROPPED so the
|
|
14
|
+
* register import resolves to its real body;
|
|
15
|
+
* 2. the gjs target's bare→scoped register routes (`fetch/register/fetch` →
|
|
16
|
+
* `@gjsify/fetch/register/fetch`, `xmlhttprequest/register` →
|
|
17
|
+
* `@gjsify/fetch/register/xhr`, …) are merged in so the inject stub's bare
|
|
18
|
+
* specifiers resolve on node exactly as they do on gjs.
|
|
19
|
+
*
|
|
20
|
+
* Applied to the BASE map only — `pluginOptions.aliases` / user aliases still
|
|
21
|
+
* override. Without the inject stub the map is untouched (the plain-Node
|
|
22
|
+
* loadability guarantee for cross-platform packages is not regressed).
|
|
23
|
+
*/
|
|
24
|
+
export declare function enableGjsRegistersForNode(baseAliases: Record<string, string>): Record<string, string>;
|
|
3
25
|
export interface NodeBuildConfig {
|
|
4
26
|
options: RolldownOptions;
|
|
5
27
|
plugins: RolldownPluginOption[];
|
package/lib/app/node.js
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
// translates to Rolldown's `output.banner` directly — Rolldown itself does
|
|
6
6
|
// not synthesise a `require()` shim for ESM consumers of bundled CJS code.
|
|
7
7
|
import { aliasPlugin } from '../plugins/alias.js';
|
|
8
|
+
import blueprintPlugin from '@gjsify/vite-plugin-blueprint';
|
|
8
9
|
import { deepkitPlugin } from '@gjsify/rolldown-plugin-deepkit';
|
|
9
|
-
import { EXTERNALS_NODE, ALIASES_GJS_FOR_NODE } from '@gjsify/resolve-npm';
|
|
10
|
+
import { EXTERNALS_NODE, ALIASES_GJS_FOR_NODE, ALIASES_WEB_FOR_GJS } from '@gjsify/resolve-npm';
|
|
10
11
|
import { getAliasesForNode } from '../utils/alias.js';
|
|
11
12
|
import { globToEntryPoints } from '../utils/entry-points.js';
|
|
12
13
|
import { nodeModulesPathRewritePlugin, getBundleDirFromOutput } from '../plugins/rewrite-node-modules-paths.js';
|
|
@@ -22,13 +23,66 @@ import { wrapInputWithSideEffects } from '../utils/entry-wrapper.js';
|
|
|
22
23
|
*/
|
|
23
24
|
const NODE_GI_GLOBALS_SPECIFIER = '@gjsify/node-gi/globals';
|
|
24
25
|
/**
|
|
25
|
-
* The node-gi standalone GJS built-in modules. The bare `system` / `gettext`
|
|
26
|
-
* specifiers a GJS source imports are aliased to these (via
|
|
27
|
-
* applied node-target-only
|
|
28
|
-
*
|
|
29
|
-
* consumer's node_modules, never
|
|
26
|
+
* The node-gi standalone GJS built-in modules. The bare `system` / `gettext` /
|
|
27
|
+
* `cairo` specifiers a GJS source imports are aliased to these (via
|
|
28
|
+
* `ALIASES_GJS_FOR_NODE`, applied node-target-only by `gjsBuiltinModulesNodePlugin`)
|
|
29
|
+
* and, like `@gjsify/node-gi/gi`, kept EXTERNAL — they load the native node-gi
|
|
30
|
+
* backend and must resolve at runtime against the consumer's node_modules, never
|
|
31
|
+
* be bundled.
|
|
32
|
+
*
|
|
33
|
+
* DERIVED from `ALIASES_GJS_FOR_NODE`'s VALUES so this set can never drift from
|
|
34
|
+
* the alias map again — a new bare built-in added there is externalised
|
|
35
|
+
* automatically. That completeness is LOAD-BEARING under `@gjsify/rolldown-native`
|
|
36
|
+
* (the GJS bundler engine): its JSON options boundary silently DROPS the resolveId
|
|
37
|
+
* `{ external: true }` flag that `gjsBuiltinModulesNodePlugin` returns (only the
|
|
38
|
+
* plain `external` string array survives serialization — the same reason
|
|
39
|
+
* `app/gjs.ts` routes its externals through a resolveId hook AND filters the array).
|
|
40
|
+
* So the rewritten `@gjsify/node-gi/<mod>` target stays external ONLY if it is in
|
|
41
|
+
* this array. `@gjsify/node-gi/cairo` was previously MISSING here (only `system` +
|
|
42
|
+
* `gettext` were listed), so under the GJS bundler `cairo` was left as a bare,
|
|
43
|
+
* unresolvable specifier — breaking every Cairo/PangoCairo consumer
|
|
44
|
+
* (`@gjsify/canvas2d-core`) built via the reverse bridge. Under npm `rolldown`
|
|
45
|
+
* (Node) the resolveId `external` flag IS honoured, so the gap was invisible until
|
|
46
|
+
* a Cairo consumer was built on the GJS bundler. e2e: `tests/e2e/node-gi-build`;
|
|
47
|
+
* unit: `packages/infra/cli/src/node-gi-externals.spec.ts`.
|
|
30
48
|
*/
|
|
31
|
-
const NODE_GI_BARE_MODULE_SPECIFIERS =
|
|
49
|
+
const NODE_GI_BARE_MODULE_SPECIFIERS = Object.values(ALIASES_GJS_FOR_NODE);
|
|
50
|
+
/** Matches a register subpath in an alias key: `<pkg>/register` or `<pkg>/register/<feature>`. */
|
|
51
|
+
const REGISTER_SUBPATH_RE = /\/register(\/|$)/;
|
|
52
|
+
/**
|
|
53
|
+
* Reverse-bridge register routing: a node build with an EXPLICIT `--globals`
|
|
54
|
+
* register-inject stub (`pluginOptions.autoGlobalsInject`, set by the CLI for
|
|
55
|
+
* `--app node` when the user names globals — e.g. `--globals auto,dom`) is a
|
|
56
|
+
* genuine GJS source that needs the REAL `@gjsify/*` register bodies (document,
|
|
57
|
+
* HTMLCanvasElement, matchMedia, …) running over `@gjsify/node-gi`, not the
|
|
58
|
+
* default `@gjsify/empty` stubs that keep cross-platform node bundles loadable
|
|
59
|
+
* on plain Node.
|
|
60
|
+
*
|
|
61
|
+
* Two adjustments over the standard node alias map:
|
|
62
|
+
* 1. every `<pkg>/register…` → `@gjsify/empty` entry is DROPPED so the
|
|
63
|
+
* register import resolves to its real body;
|
|
64
|
+
* 2. the gjs target's bare→scoped register routes (`fetch/register/fetch` →
|
|
65
|
+
* `@gjsify/fetch/register/fetch`, `xmlhttprequest/register` →
|
|
66
|
+
* `@gjsify/fetch/register/xhr`, …) are merged in so the inject stub's bare
|
|
67
|
+
* specifiers resolve on node exactly as they do on gjs.
|
|
68
|
+
*
|
|
69
|
+
* Applied to the BASE map only — `pluginOptions.aliases` / user aliases still
|
|
70
|
+
* override. Without the inject stub the map is untouched (the plain-Node
|
|
71
|
+
* loadability guarantee for cross-platform packages is not regressed).
|
|
72
|
+
*/
|
|
73
|
+
export function enableGjsRegistersForNode(baseAliases) {
|
|
74
|
+
const out = {};
|
|
75
|
+
for (const [key, value] of Object.entries(baseAliases)) {
|
|
76
|
+
if (value === '@gjsify/empty' && REGISTER_SUBPATH_RE.test(key))
|
|
77
|
+
continue;
|
|
78
|
+
out[key] = value;
|
|
79
|
+
}
|
|
80
|
+
for (const [key, value] of Object.entries(ALIASES_WEB_FOR_GJS)) {
|
|
81
|
+
if (REGISTER_SUBPATH_RE.test(key))
|
|
82
|
+
out[key] = value;
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
32
86
|
export const setupForNode = async (input) => {
|
|
33
87
|
const userExternal = input.userExternal ?? [];
|
|
34
88
|
// node-datachannel is a native C++ addon that cannot be bundled — its
|
|
@@ -89,10 +143,17 @@ export const setupForNode = async (input) => {
|
|
|
89
143
|
const sideEffectImports = [];
|
|
90
144
|
if (input.pluginOptions.nodeGiGlobalsInject)
|
|
91
145
|
sideEffectImports.push(NODE_GI_GLOBALS_SPECIFIER);
|
|
146
|
+
// The explicit `--globals` register-inject stub (reverse bridge, see
|
|
147
|
+
// enableGjsRegistersForNode). Ordered AFTER the ambient-globals shim so
|
|
148
|
+
// registers may rely on `print`/`imports` being installed.
|
|
149
|
+
const registerInject = input.pluginOptions.autoGlobalsInject;
|
|
150
|
+
if (registerInject)
|
|
151
|
+
sideEffectImports.push(registerInject);
|
|
92
152
|
const virtualEntries = wrapInputWithSideEffects(entryPoints, sideEffectImports);
|
|
93
153
|
const finalInput = virtualEntries.input;
|
|
154
|
+
const baseAliases = getAliasesForNode({ external });
|
|
94
155
|
const aliasMap = {
|
|
95
|
-
...
|
|
156
|
+
...(registerInject ? enableGjsRegistersForNode(baseAliases) : baseAliases),
|
|
96
157
|
...input.pluginOptions.aliases,
|
|
97
158
|
...input.userAliases,
|
|
98
159
|
};
|
|
@@ -163,10 +224,13 @@ export const setupForNode = async (input) => {
|
|
|
163
224
|
// runtime, so a real GJS/GI source builds + runs on Node. It returns
|
|
164
225
|
// null for `@girs/*`.
|
|
165
226
|
gjsGiNodePlugin(),
|
|
166
|
-
// Bare GJS built-in modules (`system`/`gettext`): claimed BEFORE
|
|
167
|
-
// aliasPlugin so they resolve to the EXTERNAL `@gjsify/node-gi/<mod>`
|
|
227
|
+
// Bare GJS built-in modules (`system`/`gettext`/`cairo`): claimed BEFORE
|
|
228
|
+
// the aliasPlugin so they resolve to the EXTERNAL `@gjsify/node-gi/<mod>`
|
|
168
229
|
// shims without a disk resolution (works without node-gi installed,
|
|
169
|
-
// like gi://). Node-target-only — never active on gjs/browser/ns.
|
|
230
|
+
// like gi://). Node-target-only — never active on gjs/browser/ns. The
|
|
231
|
+
// externalisation itself rides `NODE_GI_BARE_MODULE_SPECIFIERS` in the
|
|
232
|
+
// `exactExternal` array (the resolveId `external` flag is dropped under
|
|
233
|
+
// @gjsify/rolldown-native — see that const's doc-comment).
|
|
170
234
|
gjsBuiltinModulesNodePlugin(ALIASES_GJS_FOR_NODE),
|
|
171
235
|
// gjsImportsEmptyPlugin then decides the fate of `@girs/*` before
|
|
172
236
|
// `aliasPlugin` and the default resolver. Same composition order as
|
|
@@ -185,8 +249,19 @@ export const setupForNode = async (input) => {
|
|
|
185
249
|
// from the @girs package's own `gi://` specifier). `gi://*` is
|
|
186
250
|
// already claimed by gjsGiNodePlugin, so this plugin's `gi://`
|
|
187
251
|
// branch is dead on the node target either way.
|
|
188
|
-
|
|
252
|
+
// `emptyGirs` also lifts for the explicit-register reverse-bridge case:
|
|
253
|
+
// the injected register bodies (dom-elements, canvas2d, …) are genuine
|
|
254
|
+
// GJS code whose `@girs/*` value imports must route through to
|
|
255
|
+
// `requireGi`, exactly like the ambient-globals case.
|
|
256
|
+
gjsImportsEmptyPlugin({
|
|
257
|
+
emptyGirs: !(input.pluginOptions.nodeGiGlobalsInject || registerInject),
|
|
258
|
+
}),
|
|
189
259
|
aliasPlugin({ entries: flattenAliases(aliasMap) }),
|
|
260
|
+
// Blueprint (.blp → XML string): the reverse bridge runs REAL GTK on
|
|
261
|
+
// Node, so a GJS app entry (composite-template windows) must build for
|
|
262
|
+
// `--app node` too. Only claims `.blp` files — inert for plain-Node
|
|
263
|
+
// bundles. Same plugin the gjs/browser targets compose.
|
|
264
|
+
blueprintPlugin(),
|
|
190
265
|
deepkitPlugin({ reflection: input.pluginOptions.reflection }),
|
|
191
266
|
cssAsStringPlugin(),
|
|
192
267
|
nodeModulesPathRewritePlugin({ bundleDir }),
|
|
@@ -9,14 +9,21 @@ export declare function giNodeShimSource(namespace: string, version?: string): s
|
|
|
9
9
|
export declare function gjsGiNodePlugin(): Plugin;
|
|
10
10
|
/**
|
|
11
11
|
* For `--app node`: rewrite the bare GJS built-in module specifiers (`system`,
|
|
12
|
-
* `gettext`) to their `@gjsify/node-gi` reverse-bridge shims, kept
|
|
12
|
+
* `gettext`, `cairo`) to their `@gjsify/node-gi` reverse-bridge shims, kept
|
|
13
|
+
* EXTERNAL.
|
|
13
14
|
*
|
|
14
|
-
* GJS exposes `system`/`gettext` as built-in ESM modules
|
|
15
|
+
* GJS exposes `system`/`gettext`/`cairo` as built-in ESM modules
|
|
15
16
|
* (`import System from 'system'`); Node has no equivalent. This resolves the
|
|
16
|
-
* bare specifier to `@gjsify/node-gi/system` / `@gjsify/node-gi/gettext`
|
|
17
|
-
* marks it external so it is NOT bundled — resolved
|
|
18
|
-
* consumer's node_modules, exactly like
|
|
19
|
-
* rewrite) and `@gjsify/node-gi/globals`.
|
|
17
|
+
* bare specifier to `@gjsify/node-gi/system` / `@gjsify/node-gi/gettext` /
|
|
18
|
+
* `@gjsify/node-gi/cairo` and marks it external so it is NOT bundled — resolved
|
|
19
|
+
* at runtime against the consumer's node_modules, exactly like
|
|
20
|
+
* `@gjsify/node-gi/gi` (the `gi://` rewrite) and `@gjsify/node-gi/globals`.
|
|
21
|
+
*
|
|
22
|
+
* NOTE: the `{ external: true }` flag returned here is honoured by npm `rolldown`
|
|
23
|
+
* but DROPPED by `@gjsify/rolldown-native` at its JSON options boundary, so
|
|
24
|
+
* `app/node.ts` ALSO lists these targets in its `exactExternal` string array
|
|
25
|
+
* (`NODE_GI_BARE_MODULE_SPECIFIERS`, derived from `ALIASES_GJS_FOR_NODE`) — the
|
|
26
|
+
* array is what actually keeps them external under the GJS bundler engine.
|
|
20
27
|
*
|
|
21
28
|
* Returning `{ external: true }` from `resolveId` is the form honoured by BOTH
|
|
22
29
|
* bundler engines (npm rolldown on Node AND `@gjsify/rolldown-native` on GJS,
|
|
@@ -96,14 +96,21 @@ export function gjsGiNodePlugin() {
|
|
|
96
96
|
}
|
|
97
97
|
/**
|
|
98
98
|
* For `--app node`: rewrite the bare GJS built-in module specifiers (`system`,
|
|
99
|
-
* `gettext`) to their `@gjsify/node-gi` reverse-bridge shims, kept
|
|
99
|
+
* `gettext`, `cairo`) to their `@gjsify/node-gi` reverse-bridge shims, kept
|
|
100
|
+
* EXTERNAL.
|
|
100
101
|
*
|
|
101
|
-
* GJS exposes `system`/`gettext` as built-in ESM modules
|
|
102
|
+
* GJS exposes `system`/`gettext`/`cairo` as built-in ESM modules
|
|
102
103
|
* (`import System from 'system'`); Node has no equivalent. This resolves the
|
|
103
|
-
* bare specifier to `@gjsify/node-gi/system` / `@gjsify/node-gi/gettext`
|
|
104
|
-
* marks it external so it is NOT bundled — resolved
|
|
105
|
-
* consumer's node_modules, exactly like
|
|
106
|
-
* rewrite) and `@gjsify/node-gi/globals`.
|
|
104
|
+
* bare specifier to `@gjsify/node-gi/system` / `@gjsify/node-gi/gettext` /
|
|
105
|
+
* `@gjsify/node-gi/cairo` and marks it external so it is NOT bundled — resolved
|
|
106
|
+
* at runtime against the consumer's node_modules, exactly like
|
|
107
|
+
* `@gjsify/node-gi/gi` (the `gi://` rewrite) and `@gjsify/node-gi/globals`.
|
|
108
|
+
*
|
|
109
|
+
* NOTE: the `{ external: true }` flag returned here is honoured by npm `rolldown`
|
|
110
|
+
* but DROPPED by `@gjsify/rolldown-native` at its JSON options boundary, so
|
|
111
|
+
* `app/node.ts` ALSO lists these targets in its `exactExternal` string array
|
|
112
|
+
* (`NODE_GI_BARE_MODULE_SPECIFIERS`, derived from `ALIASES_GJS_FOR_NODE`) — the
|
|
113
|
+
* array is what actually keeps them external under the GJS bundler engine.
|
|
107
114
|
*
|
|
108
115
|
* Returning `{ external: true }` from `resolveId` is the form honoured by BOTH
|
|
109
116
|
* bundler engines (npm rolldown on Node AND `@gjsify/rolldown-native` on GJS,
|
package/lib/utils/runtime.d.ts
CHANGED
|
@@ -1,18 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `true` when running under Bun.
|
|
3
|
+
*
|
|
4
|
+
* Bun exposes a `globalThis.Bun` object no other runtime provides. Probed
|
|
5
|
+
* FIRST in {@link hostRuntime} because Bun also fakes `process.versions.node`
|
|
6
|
+
* for npm compatibility — a bare `process.versions.node` read is a false Node
|
|
7
|
+
* positive there.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isBun(): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* `true` when running under Deno.
|
|
12
|
+
*
|
|
13
|
+
* Deno exposes a `globalThis.Deno` object no other runtime provides. Like Bun,
|
|
14
|
+
* Deno also fakes `process.versions.node`, so it must be probed before the Node
|
|
15
|
+
* fallback.
|
|
16
|
+
*/
|
|
17
|
+
export declare function isDeno(): boolean;
|
|
1
18
|
/**
|
|
2
19
|
* `true` when running under GJS (GNOME JavaScript / SpiderMonkey).
|
|
3
20
|
*
|
|
4
21
|
* Detected via the GObject-Introspection bridge `globalThis.imports.gi`, which
|
|
5
|
-
* only exists under GJS — Node and browsers never expose it. This is
|
|
6
|
-
* canonical gjsify host probe; prefer it over re-inlining the `imports.gi`
|
|
22
|
+
* only exists under GJS — Node, Bun, Deno and browsers never expose it. This is
|
|
23
|
+
* the canonical gjsify host probe; prefer it over re-inlining the `imports.gi`
|
|
7
24
|
* check.
|
|
8
25
|
*/
|
|
9
26
|
export declare function isGjs(): boolean;
|
|
10
27
|
/**
|
|
11
|
-
* `
|
|
28
|
+
* The JS runtime hosting the gjsify tooling: `gjs`, `node`, `bun` or `deno`.
|
|
29
|
+
*
|
|
30
|
+
* The single source of truth for host-derived defaults (the default `--app`
|
|
31
|
+
* build target and the default `--runtime` in `run`/`showcase`/`storybook`).
|
|
12
32
|
*
|
|
13
|
-
*
|
|
14
|
-
* `process.versions.node`
|
|
15
|
-
* `process.versions.node`
|
|
16
|
-
*
|
|
33
|
+
* Probe order mirrors `packages/node-gi/node-gi/index.js`: **Bun and Deno
|
|
34
|
+
* first** because both fake `process.versions.node` for npm compatibility (so a
|
|
35
|
+
* `process.versions.node` read is a false Node positive there), then GJS (its
|
|
36
|
+
* `@gjsify/process` shim likewise fakes `process.versions.node`), then real
|
|
37
|
+
* Node. Falls back to `node` when nothing matches (e.g. an exotic host) — the
|
|
38
|
+
* gjsify tooling only runs on these four.
|
|
39
|
+
*/
|
|
40
|
+
export declare function hostRuntime(): 'gjs' | 'node' | 'bun' | 'deno';
|
|
41
|
+
/**
|
|
42
|
+
* `true` when running under Node.js — and NOT Bun or Deno.
|
|
43
|
+
*
|
|
44
|
+
* Bun and Deno both set `process.versions.node` for npm compatibility, so a
|
|
45
|
+
* bare `process.versions.node` probe is a false Node positive on either — the
|
|
46
|
+
* historical bug this guard fixes. GJS (via `@gjsify/process`) does the same.
|
|
47
|
+
* Guarding on all three keeps the four host predicates mutually exclusive: a
|
|
48
|
+
* host is exactly one of gjs / node / bun / deno.
|
|
17
49
|
*/
|
|
18
50
|
export declare function isNode(): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* The raw GJS runtime version from `imports.system.version` (e.g. `18800` for
|
|
53
|
+
* GJS 1.88.0), or `undefined` when not running under GJS.
|
|
54
|
+
*
|
|
55
|
+
* A distinct probe from {@link isGjs}: use it where the caller needs the numeric
|
|
56
|
+
* version (to format a banner) OR an early GJS short-circuit that must not rely
|
|
57
|
+
* on `@gjsify/process`'s faked `process.versions.node`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function gjsSystemVersion(): number | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Force a hard exit under GJS via `imports.system.exit(code)`.
|
|
62
|
+
*
|
|
63
|
+
* GJS has no atexit hook and would exit 0 at natural shutdown, so a non-zero
|
|
64
|
+
* `process.exitCode` alone does not stick — the CLI must call `system.exit`
|
|
65
|
+
* explicitly there. Node never exposes `imports`, so this is a no-op returning
|
|
66
|
+
* `false`, letting callers fall back to `process.exit`/`process.exitCode`.
|
|
67
|
+
*
|
|
68
|
+
* @returns `true` when the GJS exit was invoked (running under GJS); `false` on
|
|
69
|
+
* Node (the caller owns the Node exit path).
|
|
70
|
+
*/
|
|
71
|
+
export declare function gjsExit(code: number): boolean;
|
package/lib/utils/runtime.js
CHANGED
|
@@ -12,28 +12,102 @@
|
|
|
12
12
|
// so it stays loadable on every host and so consumers can import it via the
|
|
13
13
|
// `@gjsify/rolldown-plugin-gjsify/runtime` subpath without pulling the rest of
|
|
14
14
|
// the plugin (which transitively loads blueprint-compiler, deepkit, etc.).
|
|
15
|
+
/**
|
|
16
|
+
* `true` when running under Bun.
|
|
17
|
+
*
|
|
18
|
+
* Bun exposes a `globalThis.Bun` object no other runtime provides. Probed
|
|
19
|
+
* FIRST in {@link hostRuntime} because Bun also fakes `process.versions.node`
|
|
20
|
+
* for npm compatibility — a bare `process.versions.node` read is a false Node
|
|
21
|
+
* positive there.
|
|
22
|
+
*/
|
|
23
|
+
export function isBun() {
|
|
24
|
+
return typeof globalThis.Bun !== 'undefined';
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* `true` when running under Deno.
|
|
28
|
+
*
|
|
29
|
+
* Deno exposes a `globalThis.Deno` object no other runtime provides. Like Bun,
|
|
30
|
+
* Deno also fakes `process.versions.node`, so it must be probed before the Node
|
|
31
|
+
* fallback.
|
|
32
|
+
*/
|
|
33
|
+
export function isDeno() {
|
|
34
|
+
return typeof globalThis.Deno !== 'undefined';
|
|
35
|
+
}
|
|
15
36
|
/**
|
|
16
37
|
* `true` when running under GJS (GNOME JavaScript / SpiderMonkey).
|
|
17
38
|
*
|
|
18
39
|
* Detected via the GObject-Introspection bridge `globalThis.imports.gi`, which
|
|
19
|
-
* only exists under GJS — Node and browsers never expose it. This is
|
|
20
|
-
* canonical gjsify host probe; prefer it over re-inlining the `imports.gi`
|
|
40
|
+
* only exists under GJS — Node, Bun, Deno and browsers never expose it. This is
|
|
41
|
+
* the canonical gjsify host probe; prefer it over re-inlining the `imports.gi`
|
|
21
42
|
* check.
|
|
22
43
|
*/
|
|
23
44
|
export function isGjs() {
|
|
24
45
|
return typeof globalThis.imports?.gi !== 'undefined';
|
|
25
46
|
}
|
|
26
47
|
/**
|
|
27
|
-
* `
|
|
48
|
+
* The JS runtime hosting the gjsify tooling: `gjs`, `node`, `bun` or `deno`.
|
|
49
|
+
*
|
|
50
|
+
* The single source of truth for host-derived defaults (the default `--app`
|
|
51
|
+
* build target and the default `--runtime` in `run`/`showcase`/`storybook`).
|
|
28
52
|
*
|
|
29
|
-
*
|
|
30
|
-
* `process.versions.node`
|
|
31
|
-
* `process.versions.node`
|
|
32
|
-
*
|
|
53
|
+
* Probe order mirrors `packages/node-gi/node-gi/index.js`: **Bun and Deno
|
|
54
|
+
* first** because both fake `process.versions.node` for npm compatibility (so a
|
|
55
|
+
* `process.versions.node` read is a false Node positive there), then GJS (its
|
|
56
|
+
* `@gjsify/process` shim likewise fakes `process.versions.node`), then real
|
|
57
|
+
* Node. Falls back to `node` when nothing matches (e.g. an exotic host) — the
|
|
58
|
+
* gjsify tooling only runs on these four.
|
|
33
59
|
*/
|
|
34
|
-
export function
|
|
60
|
+
export function hostRuntime() {
|
|
61
|
+
if (isBun())
|
|
62
|
+
return 'bun';
|
|
63
|
+
if (isDeno())
|
|
64
|
+
return 'deno';
|
|
35
65
|
if (isGjs())
|
|
66
|
+
return 'gjs';
|
|
67
|
+
return 'node';
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* `true` when running under Node.js — and NOT Bun or Deno.
|
|
71
|
+
*
|
|
72
|
+
* Bun and Deno both set `process.versions.node` for npm compatibility, so a
|
|
73
|
+
* bare `process.versions.node` probe is a false Node positive on either — the
|
|
74
|
+
* historical bug this guard fixes. GJS (via `@gjsify/process`) does the same.
|
|
75
|
+
* Guarding on all three keeps the four host predicates mutually exclusive: a
|
|
76
|
+
* host is exactly one of gjs / node / bun / deno.
|
|
77
|
+
*/
|
|
78
|
+
export function isNode() {
|
|
79
|
+
if (isBun() || isDeno() || isGjs())
|
|
36
80
|
return false;
|
|
37
81
|
const proc = globalThis.process;
|
|
38
82
|
return typeof proc?.versions?.node === 'string';
|
|
39
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* The raw GJS runtime version from `imports.system.version` (e.g. `18800` for
|
|
86
|
+
* GJS 1.88.0), or `undefined` when not running under GJS.
|
|
87
|
+
*
|
|
88
|
+
* A distinct probe from {@link isGjs}: use it where the caller needs the numeric
|
|
89
|
+
* version (to format a banner) OR an early GJS short-circuit that must not rely
|
|
90
|
+
* on `@gjsify/process`'s faked `process.versions.node`.
|
|
91
|
+
*/
|
|
92
|
+
export function gjsSystemVersion() {
|
|
93
|
+
return globalThis.imports?.system?.version;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Force a hard exit under GJS via `imports.system.exit(code)`.
|
|
97
|
+
*
|
|
98
|
+
* GJS has no atexit hook and would exit 0 at natural shutdown, so a non-zero
|
|
99
|
+
* `process.exitCode` alone does not stick — the CLI must call `system.exit`
|
|
100
|
+
* explicitly there. Node never exposes `imports`, so this is a no-op returning
|
|
101
|
+
* `false`, letting callers fall back to `process.exit`/`process.exitCode`.
|
|
102
|
+
*
|
|
103
|
+
* @returns `true` when the GJS exit was invoked (running under GJS); `false` on
|
|
104
|
+
* Node (the caller owns the Node exit path).
|
|
105
|
+
*/
|
|
106
|
+
export function gjsExit(code) {
|
|
107
|
+
const sys = globalThis.imports?.system;
|
|
108
|
+
if (sys?.exit) {
|
|
109
|
+
sys.exit(code);
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
@@ -46,6 +46,19 @@ export declare function isRegisterPathResolvable(registerPath: string, fromDir:
|
|
|
46
46
|
* the gap visible so the developer can add the dep explicitly.
|
|
47
47
|
*/
|
|
48
48
|
export declare function filterResolvableRegisterPaths(registerPaths: Set<string>, fromDir: string): Set<string>;
|
|
49
|
+
/** Options for {@link writeRegisterInjectFile}. */
|
|
50
|
+
export interface WriteRegisterInjectOptions {
|
|
51
|
+
/**
|
|
52
|
+
* Reverse-bridge (`--app node`) force-override: before the injected register
|
|
53
|
+
* bodies run, clear the runtime-native web globals gjsify must OWN on the
|
|
54
|
+
* reverse bridge (the `REVERSE_BRIDGE_NATIVE_OVERRIDE_IDENTS` set — today the
|
|
55
|
+
* `fetch` family). A native is cleared ONLY when its register is actually in
|
|
56
|
+
* `registerPaths`, so no host global is deleted without a gjsify replacement.
|
|
57
|
+
* No-op (and byte-unchanged output) for the GJS target or when none of the
|
|
58
|
+
* override identifiers are injected.
|
|
59
|
+
*/
|
|
60
|
+
reverseBridgeOverride?: boolean;
|
|
61
|
+
}
|
|
49
62
|
/**
|
|
50
63
|
* Write a stub ESM file with `import` statements for the given register
|
|
51
64
|
* paths and return its absolute path, suitable for passing to esbuild's
|
|
@@ -56,5 +69,13 @@ export declare function filterResolvableRegisterPaths(registerPaths: Set<string>
|
|
|
56
69
|
*
|
|
57
70
|
* The file name is hashed by content so repeated builds with the same
|
|
58
71
|
* set reuse the same file (no churn, idempotent on disk).
|
|
72
|
+
*
|
|
73
|
+
* On the reverse bridge (`options.reverseBridgeOverride`, `--app node`) the stub
|
|
74
|
+
* is prefixed with a side-effect import of a generated clear-natives module —
|
|
75
|
+
* imported FIRST so, by ESM depth-first source-order evaluation, the native
|
|
76
|
+
* `fetch`/`Headers`/`Request`/`Response` are deleted BEFORE the register bodies
|
|
77
|
+
* run and reinstall gjsify's versions over them. Kept in a separate module
|
|
78
|
+
* (not a leading statement) because ESM hoists the register `import`s above any
|
|
79
|
+
* statement in the same module.
|
|
59
80
|
*/
|
|
60
|
-
export declare function writeRegisterInjectFile(registerPaths: Set<string>, cwd?: string): Promise<string | null>;
|
|
81
|
+
export declare function writeRegisterInjectFile(registerPaths: Set<string>, cwd?: string, options?: WriteRegisterInjectOptions): Promise<string | null>;
|
|
@@ -14,10 +14,11 @@ import { writeFile, mkdir } from 'node:fs/promises';
|
|
|
14
14
|
import { existsSync } from 'node:fs';
|
|
15
15
|
import { createHash } from 'node:crypto';
|
|
16
16
|
import { join } from 'node:path';
|
|
17
|
-
import { GJS_GLOBALS_MAP, GJS_GLOBALS_GROUPS } from '@gjsify/resolve-npm/globals-map';
|
|
17
|
+
import { GJS_GLOBALS_MAP, GJS_GLOBALS_GROUPS, REVERSE_BRIDGE_NATIVE_OVERRIDE_IDENTS } from '@gjsify/resolve-npm/globals-map';
|
|
18
18
|
import { ALIASES_WEB_FOR_GJS } from '@gjsify/resolve-npm';
|
|
19
19
|
const GLOBALS_MAP = GJS_GLOBALS_MAP;
|
|
20
20
|
const GLOBALS_GROUPS = GJS_GLOBALS_GROUPS;
|
|
21
|
+
const NATIVE_OVERRIDE_IDENTS = REVERSE_BRIDGE_NATIVE_OVERRIDE_IDENTS;
|
|
21
22
|
/**
|
|
22
23
|
* Resolve a `--globals` CLI argument into the set of `/register` subpaths
|
|
23
24
|
* that must be injected into the build.
|
|
@@ -166,6 +167,21 @@ export function filterResolvableRegisterPaths(registerPaths, fromDir) {
|
|
|
166
167
|
}
|
|
167
168
|
return out;
|
|
168
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Identifiers whose native global must be cleared before the injected registers
|
|
172
|
+
* run, given the set of register paths being injected. Only identifiers whose
|
|
173
|
+
* register is actually present are returned — clearing a native without a
|
|
174
|
+
* gjsify replacement in the same build would leave a hole.
|
|
175
|
+
*/
|
|
176
|
+
function nativeOverrideIdentsFor(registerPaths) {
|
|
177
|
+
const out = [];
|
|
178
|
+
for (const id of NATIVE_OVERRIDE_IDENTS) {
|
|
179
|
+
const registerPath = GLOBALS_MAP[id];
|
|
180
|
+
if (registerPath && registerPaths.has(registerPath))
|
|
181
|
+
out.push(id);
|
|
182
|
+
}
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
169
185
|
/**
|
|
170
186
|
* Write a stub ESM file with `import` statements for the given register
|
|
171
187
|
* paths and return its absolute path, suitable for passing to esbuild's
|
|
@@ -176,15 +192,37 @@ export function filterResolvableRegisterPaths(registerPaths, fromDir) {
|
|
|
176
192
|
*
|
|
177
193
|
* The file name is hashed by content so repeated builds with the same
|
|
178
194
|
* set reuse the same file (no churn, idempotent on disk).
|
|
195
|
+
*
|
|
196
|
+
* On the reverse bridge (`options.reverseBridgeOverride`, `--app node`) the stub
|
|
197
|
+
* is prefixed with a side-effect import of a generated clear-natives module —
|
|
198
|
+
* imported FIRST so, by ESM depth-first source-order evaluation, the native
|
|
199
|
+
* `fetch`/`Headers`/`Request`/`Response` are deleted BEFORE the register bodies
|
|
200
|
+
* run and reinstall gjsify's versions over them. Kept in a separate module
|
|
201
|
+
* (not a leading statement) because ESM hoists the register `import`s above any
|
|
202
|
+
* statement in the same module.
|
|
179
203
|
*/
|
|
180
|
-
export async function writeRegisterInjectFile(registerPaths, cwd = process.cwd()) {
|
|
204
|
+
export async function writeRegisterInjectFile(registerPaths, cwd = process.cwd(), options = {}) {
|
|
181
205
|
if (registerPaths.size === 0)
|
|
182
206
|
return null;
|
|
183
|
-
const sorted = [...registerPaths].sort();
|
|
184
|
-
const content = sorted.map((p) => `import '${p}';`).join('\n') + '\n';
|
|
185
|
-
const hash = createHash('sha1').update(content).digest('hex').slice(0, 10);
|
|
186
207
|
const cacheDir = join(cwd, 'node_modules', '.cache', 'gjsify');
|
|
187
208
|
await mkdir(cacheDir, { recursive: true });
|
|
209
|
+
const sorted = [...registerPaths].sort();
|
|
210
|
+
const lines = [];
|
|
211
|
+
const overrideIdents = options.reverseBridgeOverride ? nativeOverrideIdentsFor(registerPaths) : [];
|
|
212
|
+
if (overrideIdents.length > 0) {
|
|
213
|
+
const list = JSON.stringify(overrideIdents.sort());
|
|
214
|
+
const clearContent = '// gjsify --app node reverse-bridge: clear runtime-native web globals so the\n' +
|
|
215
|
+
'// injected @gjsify/* register bodies install their GJS-semantics versions.\n' +
|
|
216
|
+
`for (const __k of ${list}) { try { delete globalThis[__k]; } catch {} }\n`;
|
|
217
|
+
const clearHash = createHash('sha1').update(clearContent).digest('hex').slice(0, 10);
|
|
218
|
+
const clearName = `auto-globals-override-${clearHash}.mjs`;
|
|
219
|
+
await writeFile(join(cacheDir, clearName), clearContent, 'utf-8');
|
|
220
|
+
lines.push(`import './${clearName}';`);
|
|
221
|
+
}
|
|
222
|
+
for (const p of sorted)
|
|
223
|
+
lines.push(`import '${p}';`);
|
|
224
|
+
const content = lines.join('\n') + '\n';
|
|
225
|
+
const hash = createHash('sha1').update(content).digest('hex').slice(0, 10);
|
|
188
226
|
const path = join(cacheDir, `auto-globals-${hash}.mjs`);
|
|
189
227
|
await writeFile(path, content, 'utf-8');
|
|
190
228
|
return path;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/rolldown-plugin-gjsify",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Rolldown / Rollup / Vite plugin orchestrator for GJS, Node, and Browser targets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -55,11 +55,11 @@
|
|
|
55
55
|
],
|
|
56
56
|
"license": "MIT",
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@gjsify/console": "^0.
|
|
59
|
-
"@gjsify/resolve-npm": "^0.
|
|
60
|
-
"@gjsify/rolldown-plugin-deepkit": "^0.
|
|
61
|
-
"@gjsify/rolldown-plugin-pnp": "^0.
|
|
62
|
-
"@gjsify/vite-plugin-blueprint": "^0.
|
|
58
|
+
"@gjsify/console": "^0.19.0",
|
|
59
|
+
"@gjsify/resolve-npm": "^0.19.0",
|
|
60
|
+
"@gjsify/rolldown-plugin-deepkit": "^0.19.0",
|
|
61
|
+
"@gjsify/rolldown-plugin-pnp": "^0.19.0",
|
|
62
|
+
"@gjsify/vite-plugin-blueprint": "^0.19.0",
|
|
63
63
|
"@rollup/pluginutils": "^5.4.0",
|
|
64
64
|
"acorn": "^8.17.0",
|
|
65
65
|
"acorn-walk": "^8.3.5",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"sass": "^1.101.0"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
|
-
"@gjsify/lightningcss-native": "^0.
|
|
71
|
+
"@gjsify/lightningcss-native": "^0.19.0",
|
|
72
72
|
"rolldown": "^1.1.4"
|
|
73
73
|
},
|
|
74
74
|
"peerDependenciesMeta": {
|