@gjsify/rolldown-plugin-gjsify 0.36.0 → 0.38.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/gjs.d.ts +28 -54
- package/lib/app/gjs.js +124 -211
- package/lib/app/node.d.ts +31 -41
- package/lib/app/node.js +118 -221
- package/lib/plugins/css-as-string.js +10 -2
- package/lib/plugins/napi-node-addon.d.ts +46 -83
- package/lib/plugins/napi-node-addon.js +188 -308
- package/lib/plugins/process-stub.js +27 -1
- package/lib/plugins/rewrite-node-modules-paths.d.ts +12 -36
- package/lib/plugins/rewrite-node-modules-paths.js +60 -144
- package/lib/plugins/unresolved-workspace-import.d.ts +20 -38
- package/lib/plugins/unresolved-workspace-import.js +65 -157
- package/lib/shims/console-gjs.js +11 -19
- package/lib/utils/auto-globals.d.ts +56 -102
- package/lib/utils/auto-globals.js +100 -171
- package/lib/utils/detect-free-globals.d.ts +27 -46
- package/lib/utils/detect-free-globals.js +106 -185
- package/package.json +7 -7
|
@@ -1,22 +1,16 @@
|
|
|
1
|
-
// Iterative multi-pass build orchestrator for `--globals auto`.
|
|
1
|
+
// Iterative multi-pass build orchestrator for `--globals auto`. The "analyse the
|
|
2
|
+
// bundled output AFTER tree-shaking" property is bundler-agnostic and load-bearing —
|
|
3
|
+
// AGENTS.md § "Tree-shakeability invariants — permanent", plus the rationale block
|
|
4
|
+
// atop `detect-free-globals.ts`.
|
|
2
5
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
6
|
+
// Pass 1: bundle with no globals injection → parsed by acorn for free globals.
|
|
7
|
+
// Pass 2: bundle with the detected globals injected → the injected register modules
|
|
8
|
+
// pull in MORE code referencing more globals (a tree-shaking dependency
|
|
9
|
+
// cycle), so the set can still grow.
|
|
10
|
+
// Pass N: repeat until it converges, capped at MAX_ITERATIONS.
|
|
8
11
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// Pass 2: rolldown() with detected globals injected
|
|
12
|
-
// → some injected register modules pull in MORE code that
|
|
13
|
-
// references additional globals (tree-shaking dependency cycle)
|
|
14
|
-
// Pass N: repeat until the detected set converges (typically 2–3 iterations,
|
|
15
|
-
// capped at MAX_ITERATIONS=5)
|
|
16
|
-
//
|
|
17
|
-
// We deliberately do NOT minify the analysis builds: minification can
|
|
18
|
-
// alias `globalThis` to a short variable and defeat MemberExpression
|
|
19
|
-
// detection in detect-free-globals.ts.
|
|
12
|
+
// The analysis builds MUST NOT minify: minification aliases `globalThis` to a short
|
|
13
|
+
// variable and defeats MemberExpression detection in detect-free-globals.ts.
|
|
20
14
|
const defaultBundler = async ({ rolldownInput, format }) => {
|
|
21
15
|
// Indirect specifier so the GJS bundle doesn't pull npm rolldown in
|
|
22
16
|
// statically. Only reached when the caller doesn't override (Node).
|
|
@@ -46,19 +40,16 @@ const GI_BACKED = GJS_GI_BACKED_REGISTERS;
|
|
|
46
40
|
/** Maximum iterations to prevent runaway loops on pathological inputs. */
|
|
47
41
|
const MAX_ITERATIONS = 5;
|
|
48
42
|
/**
|
|
49
|
-
* Expand a detected-identifier set with the PRECOMPUTED transitive closure of
|
|
50
|
-
*
|
|
51
|
-
* `register-globals-closure` map, generated by
|
|
43
|
+
* Expand a detected-identifier set with the PRECOMPUTED transitive closure of the
|
|
44
|
+
* register modules those identifiers inject (`register-globals-closure`, generated by
|
|
52
45
|
* `packages/infra/cli/scripts/generate-register-closure.mjs`).
|
|
53
46
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* growth and the loop simply continues iterating (slower + a hint to
|
|
61
|
-
* regenerate).
|
|
47
|
+
* Passes 2..N of the plain loop exist only to discover which ADDITIONAL globals the
|
|
48
|
+
* injected registers' own code references — static, per-register information. Seeding
|
|
49
|
+
* pass 2 with the full closure makes the loop converge at the next (verification)
|
|
50
|
+
* pass, collapsing ~3-5 analysis bundles to 2. A stale or missing entry is SAFE: the
|
|
51
|
+
* verification pass sees the unexpected growth and the loop keeps iterating, slower,
|
|
52
|
+
* with a hint to regenerate.
|
|
62
53
|
*/
|
|
63
54
|
function expandWithClosure(detected, excludeSet) {
|
|
64
55
|
const out = new Set(detected);
|
|
@@ -95,16 +86,14 @@ async function applyExcludeGlobals(detected, currentInject, extraRegisterPaths,
|
|
|
95
86
|
for (const id of excludeGlobals)
|
|
96
87
|
detected.delete(id);
|
|
97
88
|
}
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
// GI-backed diagnostic below fires whether or not excludeGlobals is set.
|
|
89
|
+
// Computed for BOTH paths so the GI-backed diagnostic fires whether or not
|
|
90
|
+
// excludeGlobals is set.
|
|
101
91
|
const finalPaths = detectedToRegisterPaths(detected);
|
|
102
92
|
for (const p of extraRegisterPaths)
|
|
103
93
|
finalPaths.add(p);
|
|
104
94
|
emitGiBackedDiagnostic(finalPaths, detected, cwd);
|
|
105
|
-
// No exclude: `currentInject` already reflects `finalPaths
|
|
106
|
-
//
|
|
107
|
-
// emit its skip warnings a second time.
|
|
95
|
+
// No exclude: `currentInject` already reflects `finalPaths`, so reuse it —
|
|
96
|
+
// re-running filterResolvableRegisterPaths would repeat its skip warnings.
|
|
108
97
|
if (!excludeGlobals?.length)
|
|
109
98
|
return { detected, injectPath: currentInject };
|
|
110
99
|
const filtered = cwd ? filterResolvableRegisterPaths(finalPaths, cwd) : finalPaths;
|
|
@@ -134,18 +123,14 @@ function giNamespacesForRegister(registerPath) {
|
|
|
134
123
|
return null;
|
|
135
124
|
}
|
|
136
125
|
/**
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* `--globals auto` injecting a GI-backed register makes the bundle
|
|
142
|
-
* hard-require a GTK/GNOME runtime at load — under a GTK-less host (headless
|
|
143
|
-
* type-check container, SSR generator, plain CI) it throws `Typelib … not
|
|
144
|
-
* found`. The note names which globals triggered the injection so that
|
|
145
|
-
* otherwise-silent runtime crash is traceable at build time.
|
|
126
|
+
* The build-time note for any GI-backed register modules in a final inject set, or
|
|
127
|
+
* null when none are. Pure so it is unit-testable; `emitGiBackedDiagnostic` is the
|
|
128
|
+
* side-effecting wrapper.
|
|
146
129
|
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
130
|
+
* Injecting a GI-backed register makes the bundle hard-require a GTK/GNOME runtime at
|
|
131
|
+
* load, so under a GTK-less host (headless type-check container, SSR generator, plain
|
|
132
|
+
* CI) it throws `Typelib … not found`. Naming the triggering globals makes that
|
|
133
|
+
* otherwise-silent runtime crash traceable at build time.
|
|
149
134
|
*/
|
|
150
135
|
export function describeGiBackedInjection(registerPaths, detected) {
|
|
151
136
|
const giRegisters = new Set();
|
|
@@ -179,11 +164,10 @@ export function describeGiBackedInjection(registerPaths, detected) {
|
|
|
179
164
|
/** Emitted at most once per process so multi-pass / multi-build runs stay quiet. */
|
|
180
165
|
let giNoteEmitted = false;
|
|
181
166
|
/**
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
* not `filterResolvableRegisterPaths`, to avoid duplicating its skip warnings.
|
|
167
|
+
* Warn the GI-backed-injection note once per process. Pre-filters `registerPaths` to
|
|
168
|
+
* the ones that will ACTUALLY be injected so the note never mentions a dropped
|
|
169
|
+
* register, via `isRegisterPathResolvable` rather than
|
|
170
|
+
* `filterResolvableRegisterPaths` to avoid duplicating that one's skip warnings.
|
|
187
171
|
*/
|
|
188
172
|
function emitGiBackedDiagnostic(registerPaths, detected, cwd) {
|
|
189
173
|
if (giNoteEmitted)
|
|
@@ -206,21 +190,10 @@ function isFullConfig(v) {
|
|
|
206
190
|
return v !== null && typeof v === 'object' && !Array.isArray(v) && 'plugins' in v && 'options' in v;
|
|
207
191
|
}
|
|
208
192
|
/**
|
|
209
|
-
* Run
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
* Returns the inject stub path that the caller should pass to the
|
|
214
|
-
* final (real) build via `pluginOptions.autoGlobalsInject`.
|
|
215
|
-
*
|
|
216
|
-
* @param analysisOptions Rolldown input options for the in-memory build.
|
|
217
|
-
* @param pluginOptions Gjsify plugin options (without `autoGlobalsInject`,
|
|
218
|
-
* which this function computes).
|
|
219
|
-
* @param gjsifyPluginFactory Factory returning the gjsify plugin instance
|
|
220
|
-
* for a given set of plugin options. Provided by the caller to avoid a
|
|
221
|
-
* cyclic import between this module and `../plugin.ts`.
|
|
222
|
-
* @param verbose Emit per-iteration debug output to console.
|
|
223
|
-
* @param options Optional `extraGlobalsList` / `excludeGlobals`.
|
|
193
|
+
* Run the iterative in-memory build with acorn-based global detection, each pass
|
|
194
|
+
* seeded by the previous pass's globals, until the detected set is stable. Returns the
|
|
195
|
+
* inject stub path the caller passes to the final build via
|
|
196
|
+
* `pluginOptions.autoGlobalsInject`.
|
|
224
197
|
*/
|
|
225
198
|
export async function detectAutoGlobals(analysisOptions, pluginOptions, gjsifyPluginFactory, verbose, options = {}, bundler = defaultBundler) {
|
|
226
199
|
const extraRegisterPaths = options.extraGlobalsList
|
|
@@ -234,9 +207,8 @@ export async function detectAutoGlobals(analysisOptions, pluginOptions, gjsifyPl
|
|
|
234
207
|
const resolvableExtra = cwd ? filterResolvableRegisterPaths(extraRegisterPaths, cwd) : extraRegisterPaths;
|
|
235
208
|
currentInject = (await writeRegisterInjectFile(resolvableExtra, cwd)) ?? undefined;
|
|
236
209
|
}
|
|
237
|
-
// Caller
|
|
238
|
-
//
|
|
239
|
-
// resolvers / loaders.
|
|
210
|
+
// Caller plugins (e.g. the PnP relay) survive every pass; the gjsify plugin is
|
|
211
|
+
// appended last so its hooks run after any custom resolvers / loaders.
|
|
240
212
|
const callerPlugins = (analysisOptions.plugins ?? []).filter((p) => {
|
|
241
213
|
const name = p && typeof p === 'object' && 'name' in p ? p.name : undefined;
|
|
242
214
|
return name !== 'gjsify' && name !== 'gjsify-orchestrator';
|
|
@@ -246,13 +218,6 @@ export async function detectAutoGlobals(analysisOptions, pluginOptions, gjsifyPl
|
|
|
246
218
|
...pluginOptions,
|
|
247
219
|
autoGlobalsInject: currentInject,
|
|
248
220
|
});
|
|
249
|
-
// Two factory shapes are supported: a plain plugin array (legacy)
|
|
250
|
-
// or the orchestrator config `{ options, plugins }`. The latter
|
|
251
|
-
// gives auto-globals access to the per-app `resolve.conditionNames`
|
|
252
|
-
// / `mainFields` / `external` / `treeshake` settings — without
|
|
253
|
-
// these, native-rolldown defaults to a different module-resolution
|
|
254
|
-
// condition set than npm-rolldown, packages resolve to different
|
|
255
|
-
// entries, and the detected global set diverges between engines.
|
|
256
221
|
let gjsifyInstance;
|
|
257
222
|
let orchestratorOptions;
|
|
258
223
|
if (isFullConfig(factoryResult)) {
|
|
@@ -262,28 +227,21 @@ export async function detectAutoGlobals(analysisOptions, pluginOptions, gjsifyPl
|
|
|
262
227
|
else {
|
|
263
228
|
gjsifyInstance = factoryResult;
|
|
264
229
|
}
|
|
265
|
-
// The
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
// entry — Rolldown bundles its side effects into the output and the
|
|
271
|
-
// detector sees the resulting identifier references.
|
|
230
|
+
// The inject stub is a side-effect-only ESM file importing
|
|
231
|
+
// `<pkg>/register/<feature>` paths, appended as an ADDITIONAL ENTRY so Rolldown
|
|
232
|
+
// bundles its side effects and the detector sees the resulting references.
|
|
233
|
+
// Rolldown's `transform.inject` is the source-AST per-identifier rewrite the
|
|
234
|
+
// tree-shakeability invariants forbid.
|
|
272
235
|
const inputWithInject = currentInject
|
|
273
236
|
? appendInjectAsEntry(analysisOptions.input, currentInject)
|
|
274
237
|
: analysisOptions.input;
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
//
|
|
278
|
-
// (`analysisOptions.*`) win when explicitly set.
|
|
238
|
+
// Take resolve/external/transform from the orchestrator so the analysis bundle
|
|
239
|
+
// goes through the same module resolution as the final build; explicit
|
|
240
|
+
// analysis-side overrides win.
|
|
279
241
|
const mergedResolve = analysisOptions.resolve ?? orchestratorOptions?.resolve;
|
|
280
242
|
const mergedExternal = analysisOptions.external ?? orchestratorOptions?.external;
|
|
281
243
|
const mergedTransform = analysisOptions.transform ?? orchestratorOptions?.transform;
|
|
282
244
|
const orchTreeshake = orchestratorOptions?.treeshake;
|
|
283
|
-
// Inline-collect both halves of the plugin chain in their existing
|
|
284
|
-
// shape: caller plugins first (PnP, user text-loaders), then the
|
|
285
|
-
// gjsify chain (which may be either a single RolldownPluginOption
|
|
286
|
-
// or an array of them depending on the factory shape).
|
|
287
245
|
const gjsifyPluginsArray = Array.isArray(gjsifyInstance) ? gjsifyInstance : [gjsifyInstance];
|
|
288
246
|
const chunkCodes = await bundler({
|
|
289
247
|
rolldownInput: {
|
|
@@ -300,11 +258,9 @@ export async function detectAutoGlobals(analysisOptions, pluginOptions, gjsifyPl
|
|
|
300
258
|
if (chunkCodes.length === 0) {
|
|
301
259
|
return { detected: new Set(), injectPath: currentInject };
|
|
302
260
|
}
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
// top-level declarations: `File`, `Buffer`, …) that acorn can't
|
|
307
|
-
// parse. Per-chunk parsing keeps each chunk's lexical scope intact.
|
|
261
|
+
// Per-chunk parse, then union: Rolldown emits one chunk per entry, and
|
|
262
|
+
// concatenating them yields duplicate top-level declarations (`File`,
|
|
263
|
+
// `Buffer`, …) that acorn rejects as an invalid program.
|
|
308
264
|
const newDetected = new Set();
|
|
309
265
|
for (let i = 0; i < chunkCodes.length; i++) {
|
|
310
266
|
const code = chunkCodes[i] ?? '';
|
|
@@ -315,49 +271,40 @@ export async function detectAutoGlobals(analysisOptions, pluginOptions, gjsifyPl
|
|
|
315
271
|
catch (e) {
|
|
316
272
|
if (globalThis.process?.env
|
|
317
273
|
?.GJSIFY_DEBUG_AUTO_GLOBALS) {
|
|
318
|
-
// `tmpdir()`, not a literal `/tmp
|
|
319
|
-
//
|
|
320
|
-
// `catch
|
|
321
|
-
//
|
|
274
|
+
// `tmpdir()`, not a literal `/tmp`: on Windows that wrote to
|
|
275
|
+
// `<drive>:\tmp\`, and since the `writeFileSync` sits inside a
|
|
276
|
+
// `catch` the dump silently did not appear where the log line
|
|
277
|
+
// below says it did.
|
|
322
278
|
const os = await import('node:os');
|
|
323
279
|
const nodePath = await import('node:path');
|
|
324
280
|
const path = nodePath.join(os.tmpdir(), `gjsify-auto-globals-failed-chunk-${i}.mjs`);
|
|
325
281
|
try {
|
|
326
|
-
// Dynamic `import()`
|
|
327
|
-
//
|
|
328
|
-
// `node:fs` for it. (It is NOT a `require` — it never
|
|
329
|
-
// was; the rule-disable comment that used to sit here
|
|
330
|
-
// named a rule that cannot apply to an import
|
|
331
|
-
// expression.)
|
|
282
|
+
// Dynamic `import()` so this debug-only branch keeps `node:fs`
|
|
283
|
+
// out of the module graph.
|
|
332
284
|
const fs = await import('node:fs');
|
|
333
285
|
fs.writeFileSync(path, code);
|
|
334
286
|
console.error(`[gjsify-auto-globals] parse failed on chunk #${i} — wrote ${path} for inspection`);
|
|
335
287
|
}
|
|
336
288
|
catch {
|
|
337
|
-
//
|
|
338
|
-
//
|
|
339
|
-
//
|
|
340
|
-
// failure escape would replace the real parse error
|
|
341
|
-
// `e` — rethrown below — with a misleading one.
|
|
289
|
+
// Swallowed on purpose: writing the debug dump is best-effort
|
|
290
|
+
// (permissions, disk), and letting its failure escape would
|
|
291
|
+
// replace the real parse error `e`, rethrown below.
|
|
342
292
|
}
|
|
343
293
|
}
|
|
344
294
|
throw e;
|
|
345
295
|
}
|
|
346
296
|
}
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
//
|
|
350
|
-
// corresponding `@gjsify/<pkg>/register/<feature>` is not in the
|
|
351
|
-
// project's resolvable dep tree.
|
|
297
|
+
// Before writing the next iteration's inject file: otherwise an excluded
|
|
298
|
+
// identifier still lands in the inject import list and the analysis build fails
|
|
299
|
+
// when its `@gjsify/<pkg>/register/<feature>` is not in the project's dep tree.
|
|
352
300
|
if (excludeSet.size > 0) {
|
|
353
301
|
for (const id of excludeSet)
|
|
354
302
|
newDetected.delete(id);
|
|
355
303
|
}
|
|
356
|
-
// Fixpoint check: detection is monotonic
|
|
357
|
-
//
|
|
358
|
-
//
|
|
359
|
-
//
|
|
360
|
-
// "this pass found nothing OUTSIDE the injected set", not equality.
|
|
304
|
+
// Fixpoint check: detection is monotonic (injecting registers can only ADD
|
|
305
|
+
// references) and the closure expansion below deliberately over-approximates, so
|
|
306
|
+
// convergence is "this pass found nothing OUTSIDE the injected set", not
|
|
307
|
+
// equality.
|
|
361
308
|
if (isSubset(newDetected, detected)) {
|
|
362
309
|
if (verbose) {
|
|
363
310
|
const sorted = [...detected].sort();
|
|
@@ -366,21 +313,17 @@ export async function detectAutoGlobals(analysisOptions, pluginOptions, gjsifyPl
|
|
|
366
313
|
}
|
|
367
314
|
return applyExcludeGlobals(detected, currentInject, extraRegisterPaths, options.excludeGlobals, cwd);
|
|
368
315
|
}
|
|
369
|
-
// Seed the next pass with the
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
// Union with the previously-injected set keeps growth monotonic
|
|
373
|
-
// (an over-approximated superset must never shrink, or the subset
|
|
374
|
-
// convergence check could oscillate).
|
|
316
|
+
// Seed the next pass with the precomputed closure. Unioning with the
|
|
317
|
+
// previously-injected set keeps growth monotonic — an over-approximated superset
|
|
318
|
+
// that shrank could make the subset convergence check oscillate.
|
|
375
319
|
const expanded = options.disableClosureExpansion ? newDetected : expandWithClosure(newDetected, excludeSet);
|
|
376
320
|
if (verbose && expanded.size > newDetected.size) {
|
|
377
321
|
console.debug(`[gjsify] --globals auto: closure map expanded ${newDetected.size} → ${expanded.size} global(s)`);
|
|
378
322
|
}
|
|
379
323
|
if (iteration > 1 && verbose) {
|
|
380
|
-
//
|
|
381
|
-
//
|
|
382
|
-
//
|
|
383
|
-
// pay extra passes until the map is regenerated.
|
|
324
|
+
// A growth pass after the closure-seeded pass means the committed map no
|
|
325
|
+
// longer covers a register's references: iterating keeps it correct, but
|
|
326
|
+
// builds pay extra passes until the map is regenerated.
|
|
384
327
|
console.debug('[gjsify] --globals auto: detection grew past the closure-map expansion — the committed ' +
|
|
385
328
|
'register-globals-closure map looks stale. Regenerate via ' +
|
|
386
329
|
'`node packages/infra/cli/scripts/generate-register-closure.mjs`.');
|
|
@@ -389,11 +332,9 @@ export async function detectAutoGlobals(analysisOptions, pluginOptions, gjsifyPl
|
|
|
389
332
|
let registerPaths = detectedToRegisterPaths(detected);
|
|
390
333
|
for (const p of extraRegisterPaths)
|
|
391
334
|
registerPaths.add(p);
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
// with a warning. The caller can add the dep or use --exclude-globals
|
|
396
|
-
// to acknowledge the gap. See AGENTS.md fix (a) rationale.
|
|
335
|
+
// Drop register paths whose polyfill package is not installed: an unresolvable
|
|
336
|
+
// import is a HARD Rolldown error, strictly worse than skipping with a warning.
|
|
337
|
+
// The caller adds the dep or acknowledges the gap via --exclude-globals.
|
|
397
338
|
if (cwd)
|
|
398
339
|
registerPaths = filterResolvableRegisterPaths(registerPaths, cwd);
|
|
399
340
|
if (registerPaths.size === 0) {
|
|
@@ -420,24 +361,21 @@ export async function detectAutoGlobals(analysisOptions, pluginOptions, gjsifyPl
|
|
|
420
361
|
* 1. a GJS ambient global (`print`/`printerr`/`log`/`logError`/`ARGV`/
|
|
421
362
|
* `imports`) referenced as a BARE identifier — the legacy GJS-source shape
|
|
422
363
|
* `@gjsify/node-gi/globals` seeds (`detectGjsAmbientGlobals`);
|
|
423
|
-
* 2. a STATIC import of `@gjsify/node-gi` (root or subpath) — the shape the
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
* shim dropped (the `gjsify storybook --runtime node` no-`requireGi`
|
|
431
|
-
* regression).
|
|
364
|
+
* 2. a STATIC import of `@gjsify/node-gi` (root or subpath) — the shape the bare GJS
|
|
365
|
+
* built-ins are externalised to, i.e. the PORTABLE GJS-source spelling
|
|
366
|
+
* (`detectNodeGiModuleImports`). A bundle retaining one is already bound to the
|
|
367
|
+
* bridge at load, so flipping injection cannot cost plain-Node loadability, while
|
|
368
|
+
* a portable GJS app with no ambient global at all (`system.programArgs` instead
|
|
369
|
+
* of `ARGV`) would otherwise get its `@girs/*` bodies emptied and its globals shim
|
|
370
|
+
* dropped (the `gjsify storybook --runtime node` no-`requireGi` regression).
|
|
432
371
|
*
|
|
433
|
-
* A SINGLE pass suffices
|
|
434
|
-
*
|
|
435
|
-
*
|
|
372
|
+
* A SINGLE pass suffices, unlike the GJS register-injection loop: the shim is EXTERNAL,
|
|
373
|
+
* so injecting it adds no bundled code that could reference more globals — no
|
|
374
|
+
* tree-shaking dependency cycle to converge.
|
|
436
375
|
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
* crashes on plain Node without node-gi. See `detectGjsAmbientGlobals`.
|
|
376
|
+
* Detection is on the bundled output AFTER tree-shaking, so a `print`/`imports`
|
|
377
|
+
* reference behind statically-dead code does not trigger injection — importing the shim
|
|
378
|
+
* eagerly loads the native node-gi addon, which crashes on plain Node without node-gi.
|
|
441
379
|
*/
|
|
442
380
|
export async function detectNodeGiGlobals(analysisOptions, pluginOptions, gjsifyPluginFactory, bundler = defaultBundler) {
|
|
443
381
|
const factoryResult = await gjsifyPluginFactory(pluginOptions);
|
|
@@ -455,17 +393,12 @@ export async function detectNodeGiGlobals(analysisOptions, pluginOptions, gjsify
|
|
|
455
393
|
return name !== 'gjsify' && name !== 'gjsify-orchestrator';
|
|
456
394
|
});
|
|
457
395
|
const gjsifyPluginsArray = Array.isArray(gjsifyInstance) ? gjsifyInstance : [gjsifyInstance];
|
|
458
|
-
//
|
|
459
|
-
//
|
|
460
|
-
// build
|
|
461
|
-
// not treat `node:*` as builtins and
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
// `resolve` (conditionNames/mainFields) and `treeshake`. We strip only the
|
|
465
|
-
// disk-write `output` (the in-memory `generate({format})` handles output)
|
|
466
|
-
// and override `input`/`plugins`. Analysis-side overrides win when set.
|
|
467
|
-
// Mirrors how `detectAutoGlobals` reuses the orchestrator config, with the
|
|
468
|
-
// platform/external/resolve carried verbatim instead of cherry-picked.
|
|
396
|
+
// The FULL orchestrator options as the base (only `output` stripped, `input`/
|
|
397
|
+
// `plugins` overridden) so the analysis bundle goes through the EXACT SAME module
|
|
398
|
+
// resolution as the real `--app node` build. Most importantly `platform: 'node'`:
|
|
399
|
+
// without it Rolldown does not treat `node:*` as builtins and
|
|
400
|
+
// `@gjsify/rolldown-plugin-pnp` rejects `node:fs` with a `RESOLVE_ERROR`. Carried
|
|
401
|
+
// verbatim rather than cherry-picked, unlike `detectAutoGlobals`.
|
|
469
402
|
const baseOptions = orchestratorOptions ? { ...orchestratorOptions } : {};
|
|
470
403
|
delete baseOptions.output;
|
|
471
404
|
let chunkCodes;
|
|
@@ -484,11 +417,9 @@ export async function detectNodeGiGlobals(analysisOptions, pluginOptions, gjsify
|
|
|
484
417
|
});
|
|
485
418
|
}
|
|
486
419
|
catch (e) {
|
|
487
|
-
// Fail SAFE: a detection-pass failure must
|
|
488
|
-
//
|
|
489
|
-
//
|
|
490
|
-
// defined` at runtime, recoverable via `--globals` or an explicit
|
|
491
|
-
// `import '@gjsify/node-gi/globals'`), never a hard build failure.
|
|
420
|
+
// Fail SAFE: a detection-pass failure must never break a build that would
|
|
421
|
+
// otherwise succeed. Not injecting costs at worst a `ReferenceError: print is
|
|
422
|
+
// not defined` at runtime, recoverable via `--globals` or an explicit import.
|
|
492
423
|
console.warn('[gjsify] --app node: could not analyse the bundle for GJS ambient globals ' +
|
|
493
424
|
`(${e instanceof Error ? e.message : String(e)}); skipping @gjsify/node-gi/globals ` +
|
|
494
425
|
'injection. If this build uses print/imports/ARGV, add `import ' +
|
|
@@ -504,10 +435,8 @@ export async function detectNodeGiGlobals(analysisOptions, pluginOptions, gjsify
|
|
|
504
435
|
return false;
|
|
505
436
|
}
|
|
506
437
|
/**
|
|
507
|
-
* Append an
|
|
508
|
-
*
|
|
509
|
-
* The new entry is given the synthetic name `__gjsify_inject` when the
|
|
510
|
-
* record form is used so it doesn't collide with user-named outputs.
|
|
438
|
+
* Append an entry path to a Rolldown `input` value, preserving its shape. In the record
|
|
439
|
+
* form the synthetic name `__gjsify_inject` avoids colliding with user-named outputs.
|
|
511
440
|
*/
|
|
512
441
|
function appendInjectAsEntry(input, injectPath) {
|
|
513
442
|
if (input === undefined)
|
|
@@ -1,62 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* `--app gjs --globals auto` injection. See `scanFreeGlobals` for the rules.
|
|
2
|
+
* Free identifiers in bundled output that match the known web/Node globals in
|
|
3
|
+
* `GJS_GLOBALS_MAP` — drives `--app gjs --globals auto`. Rules: `scanFreeGlobals`.
|
|
5
4
|
*/
|
|
6
5
|
export declare function detectFreeGlobals(code: string): Set<string>;
|
|
7
6
|
/**
|
|
8
|
-
* The
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* `@gjsify/node-gi/globals` seeds these on `globalThis` when imported.
|
|
12
|
-
*
|
|
13
|
-
* This is the REVERSE of `GJS_GLOBALS_MAP` (which maps Node/Web globals onto
|
|
14
|
-
* GJS): it's the set the `--app node` target injects the node-gi globals shim
|
|
15
|
-
* for. Kept here next to the detector so the two never drift.
|
|
7
|
+
* The globals that exist implicitly under gjs but not on Node, seeded on `globalThis` by
|
|
8
|
+
* `@gjsify/node-gi/globals`. The REVERSE of `GJS_GLOBALS_MAP`: the set whose presence
|
|
9
|
+
* makes `--app node` inject that shim. Kept beside the detector so the two never drift.
|
|
16
10
|
*/
|
|
17
11
|
export declare const GJS_AMBIENT_GLOBALS: ReadonlySet<string>;
|
|
18
12
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* `@gjsify/node-gi/globals` injected.
|
|
13
|
+
* References to `GJS_AMBIENT_GLOBALS` in a bundled `--app node` chunk — the signal that
|
|
14
|
+
* the bundle needs `@gjsify/node-gi/globals`.
|
|
22
15
|
*
|
|
23
|
-
* Runs on the
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* GJS-source shape (`print(...)`, `imports.gi.Gtk`). The `globalThis.imports` /
|
|
27
|
-
* `globalThis.print` host-member form is the cross-platform isomorphic-GUARD
|
|
28
|
-
* shape (`if (typeof globalThis.imports !== 'undefined') { … }`) and is
|
|
29
|
-
* deliberately ignored: injecting `@gjsify/node-gi/globals` for it would break
|
|
30
|
-
* plain-Node loadability of a package that merely gates GJS usage (the bare
|
|
31
|
-
* external import is unresolvable without node-gi; the shim eagerly loads the
|
|
32
|
-
* native addon). Pure `typeof imports` probes are deferred too. See
|
|
33
|
-
* `ignoreHostMembers`.
|
|
16
|
+
* Runs on the output AFTER tree-shaking, so a reference behind a statically-dead branch
|
|
17
|
+
* does not trigger injection, and matches only BARE identifiers (`print(...)`,
|
|
18
|
+
* `imports.gi.Gtk`) — see `ignoreHostMembers` for why the host-member form is ignored.
|
|
34
19
|
*/
|
|
35
20
|
export declare function detectGjsAmbientGlobals(code: string): Set<string>;
|
|
36
21
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* `detectGjsAmbientGlobals`.
|
|
22
|
+
* A STATIC import of `@gjsify/node-gi` (root or subpath) in a bundled `--app node`
|
|
23
|
+
* chunk — the second genuine-GJS-source signal beside `detectGjsAmbientGlobals`.
|
|
40
24
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* `
|
|
49
|
-
* bodies and its globals shim in one go — `gjsify storybook --runtime node`
|
|
50
|
-
* emitted a bundle with no `requireGi` at all.
|
|
25
|
+
* It is a signal because the bare GJS built-ins are rewritten to
|
|
26
|
+
* `@gjsify/node-gi/<name>` and kept EXTERNAL as top-level imports: a tree-shaken graph
|
|
27
|
+
* that retains one is ALREADY bound to the bridge at load (Node resolves the external
|
|
28
|
+
* at link time and fails without node-gi), so treating it as a GJS-source build cannot
|
|
29
|
+
* cost plain-Node loadability. NOT treating it as one is how a portable GJS app
|
|
30
|
+
* spelling `ARGV` as `system.programArgs` lost its `@girs/*` bodies and its globals
|
|
31
|
+
* shim in one go — `gjsify storybook --runtime node` emitted a bundle with no
|
|
32
|
+
* `requireGi` at all.
|
|
51
33
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* and flipping injection for it would break exactly that.
|
|
34
|
+
* A surviving `gi://` import is deliberately NOT a signal: its shim loads node-gi LAZILY
|
|
35
|
+
* through a `require('@gjsify/node-gi/gi')` CALL, so this scan is structurally blind to
|
|
36
|
+
* it by design. A cross-platform package with a gjs-gated `gi://` import stays loadable
|
|
37
|
+
* on plain Node (#641, pinned by `tests/e2e/node-gi-globals-inject`), and flipping
|
|
38
|
+
* injection for it would break exactly that.
|
|
58
39
|
*
|
|
59
|
-
* Scans import/re-export STATEMENTS via the AST
|
|
60
|
-
*
|
|
40
|
+
* Scans import/re-export STATEMENTS via the AST, never text, so a quoted specifier in a
|
|
41
|
+
* test name or template cannot trigger.
|
|
61
42
|
*/
|
|
62
43
|
export declare function detectNodeGiModuleImports(code: string): boolean;
|