@gjsify/rolldown-plugin-gjsify 0.23.0 → 0.24.2
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/browser.js +24 -5
- package/lib/app/gjs.d.ts +24 -0
- package/lib/app/gjs.js +79 -28
- package/lib/app/index.d.ts +1 -1
- package/lib/app/index.js +1 -1
- package/lib/app/nativescript.js +31 -5
- package/lib/app/node.js +13 -1
- package/lib/index.d.ts +5 -1
- package/lib/index.js +3 -1
- package/lib/plugins/alias.js +36 -0
- package/lib/plugins/css-as-string.js +26 -1
- package/lib/plugins/gjs-gi-node.js +5 -1
- package/lib/plugins/gjs-imports-empty.js +2 -1
- package/lib/plugins/napi-node-addon.d.ts +25 -5
- package/lib/plugins/napi-node-addon.js +309 -52
- package/lib/plugins/unresolved-workspace-import.d.ts +105 -0
- package/lib/plugins/unresolved-workspace-import.js +318 -0
- package/lib/utils/auto-globals.js +6 -1
- package/lib/utils/entry-wrapper.js +2 -1
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.js +1 -0
- package/lib/utils/virtual-module-id.d.ts +14 -0
- package/lib/utils/virtual-module-id.js +36 -0
- package/package.json +7 -7
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
// Resolver-layer guard: an unresolvable `@gjsify/*` edge is a build ERROR,
|
|
2
|
+
// never a silent external.
|
|
3
|
+
//
|
|
4
|
+
// ## The failure this closes
|
|
5
|
+
//
|
|
6
|
+
// Rolldown (like Rollup) treats an import nothing resolves as EXTERNAL: it
|
|
7
|
+
// emits an `UNRESOLVED_IMPORT` **warning**, re-emits the ORIGINAL specifier
|
|
8
|
+
// into the output, and exits 0. For a normal bundler that is a reasonable
|
|
9
|
+
// default — the specifier might be provided by the host. For `gjsify build` it
|
|
10
|
+
// is not, because the `@gjsify/*` edge that failed is precisely the SUBSTITUTION
|
|
11
|
+
// that makes the bundle runnable on the target:
|
|
12
|
+
//
|
|
13
|
+
// import { readFileSync } from 'node:fs'
|
|
14
|
+
// → aliasPlugin rewrites to `@gjsify/fs`
|
|
15
|
+
// → `@gjsify/fs` cannot be resolved (no `node_modules`, or the workspace
|
|
16
|
+
// package has no built `lib/` so its `exports["."]` points at a file
|
|
17
|
+
// that does not exist)
|
|
18
|
+
// → aliasPlugin falls through, nothing else claims it
|
|
19
|
+
// → `node:fs` is externalised, the build "succeeds"
|
|
20
|
+
//
|
|
21
|
+
// Measured on `packages/infra/cli/dist/cli.gjs.mjs` (repo task #63): 1 bare
|
|
22
|
+
// `node:` import on `origin/main` vs 11 from a worktree with no `node_modules`
|
|
23
|
+
// — a bundle stock GJS aborts on before one line runs
|
|
24
|
+
// (`ImportError: Unsupported URI scheme for importing: node`). The pre-commit
|
|
25
|
+
// hook then rebuilt and AUTO-STAGED it, and nothing local objected: tsc, lint,
|
|
26
|
+
// format, unit and e2e all run the Node entry, never the emitted GJS bundle.
|
|
27
|
+
//
|
|
28
|
+
// ## Why this is upstream of the `--app gjs` `node:` guard (PR #828)
|
|
29
|
+
//
|
|
30
|
+
// `utils/gjs-bundle-guard.ts` reads the finished module graph and refuses to
|
|
31
|
+
// EMIT a `--app gjs` bundle that still statically imports a bare `node:`
|
|
32
|
+
// specifier. That is the right last line of defence, and it stays. But it is:
|
|
33
|
+
//
|
|
34
|
+
// - **gjs-only** — the same silent externalisation happens on `--app node`
|
|
35
|
+
// and `--app browser`, where a surviving bare `@gjsify/foo` is equally
|
|
36
|
+
// unloadable and no `node:`-shaped symptom exists to detect;
|
|
37
|
+
// - **symptom-shaped** — it names `node:fs`, not the `@gjsify/fs` edge that
|
|
38
|
+
// actually failed, and cannot name the importer;
|
|
39
|
+
// - **post-tree-shake** — an unresolved edge that gets shaken out leaves no
|
|
40
|
+
// trace at all, so the artefact passes while the build is still wrong.
|
|
41
|
+
//
|
|
42
|
+
// This guard fires at RESOLUTION time instead: it names the specifier, the
|
|
43
|
+
// substitution target, the importer, and the likely cause.
|
|
44
|
+
//
|
|
45
|
+
// ## The legitimate-vs-broken line
|
|
46
|
+
//
|
|
47
|
+
// A blanket "fail on any external" would break every real build — externals are
|
|
48
|
+
// load-bearing on every target (`gi://*`, `cairo`/`system`/`gettext` on gjs; the
|
|
49
|
+
// whole `EXTERNALS_NODE` set plus `@gjsify/node-gi*` and `node-datachannel` on
|
|
50
|
+
// node; whatever the user passed to `--external`). Those never reach the
|
|
51
|
+
// unresolved fallback by accident — a `resolveId` hook or the `external` option
|
|
52
|
+
// CLAIMS them, deliberately. So the distinction drawn here is:
|
|
53
|
+
//
|
|
54
|
+
// DECLARED external → legitimate, ignored. Asked via the target's own
|
|
55
|
+
// externals predicate (`isExternal`), i.e. exactly the
|
|
56
|
+
// policy `externalsPlugin` enforces, so the guard and the
|
|
57
|
+
// policy cannot drift.
|
|
58
|
+
// PROMISED, missing → broken. Either (a) the specifier has an entry in the
|
|
59
|
+
// target's substitution table and that target does not
|
|
60
|
+
// resolve, or (b) the specifier IS a bare `@gjsify/*`
|
|
61
|
+
// workspace package that does not resolve.
|
|
62
|
+
// anything else → not our business. A missing `lodash` is a plain
|
|
63
|
+
// dependency bug that `UNRESOLVED_IMPORT` already
|
|
64
|
+
// reports; widening to every bare specifier would turn a
|
|
65
|
+
// genuinely optional peer into a hard build failure, and
|
|
66
|
+
// this guard's whole claim is that the `@gjsify/*` edge
|
|
67
|
+
// is NOT optional — the target cannot run without it.
|
|
68
|
+
//
|
|
69
|
+
// On (a): the check is keyed on the ALIAS TARGET being a `@gjsify/*` package,
|
|
70
|
+
// not merely on the entry existing. Every value in the curated + derived tables
|
|
71
|
+
// is a `@gjsify/*` specifier today, so this is a distinction without a
|
|
72
|
+
// difference for them — but it keeps a user `--alias foo=./some/local.js` (or a
|
|
73
|
+
// shim path like `random-access-file/index.js`) on the old fall-through
|
|
74
|
+
// behaviour instead of promoting a user's own mapping mistake to a fatal error
|
|
75
|
+
// in a plugin they did not ask for.
|
|
76
|
+
//
|
|
77
|
+
// ## Not registered for `--library`
|
|
78
|
+
//
|
|
79
|
+
// Library mode PRESERVES imports on purpose (`preserveModules`, bare specifiers
|
|
80
|
+
// kept so the consumer resolves them). An unresolved `@gjsify/*` there is the
|
|
81
|
+
// contract, not a bug, so `library/lib.ts` deliberately does not compose this
|
|
82
|
+
// plugin.
|
|
83
|
+
import { dirname } from 'node:path';
|
|
84
|
+
/** Bare `@gjsify/*` package specifier — the workspace edge this guard protects. */
|
|
85
|
+
const WORKSPACE_SCOPE = '@gjsify/';
|
|
86
|
+
/**
|
|
87
|
+
* How many reverse-looked-up source specifiers the error may name before the
|
|
88
|
+
* list stops being evidence and becomes noise. A per-builtin target inverts to
|
|
89
|
+
* 2 (`fs` + `node:fs`); a shared sink like `@gjsify/empty` inverts to ~50.
|
|
90
|
+
*/
|
|
91
|
+
const MAX_REPORTED_ALIAS_SOURCES = 4;
|
|
92
|
+
/**
|
|
93
|
+
* Is `id` a bare `@gjsify/*` package specifier (root or subpath)?
|
|
94
|
+
*
|
|
95
|
+
* Deliberately excludes disk paths and synthetic ids: a resolved
|
|
96
|
+
* `/…/node_modules/@gjsify/fs/lib/esm/index.js` is already resolved, and a
|
|
97
|
+
* `\0gjsify-*` virtual id belongs to the plugin that emitted it.
|
|
98
|
+
*/
|
|
99
|
+
export function isWorkspaceSpecifier(id) {
|
|
100
|
+
if (!id.startsWith(WORKSPACE_SCOPE))
|
|
101
|
+
return false;
|
|
102
|
+
if (id.includes('\0'))
|
|
103
|
+
return false;
|
|
104
|
+
// `@gjsify/fs` and `@gjsify/fs/register/x` qualify; `@gjsify/` and
|
|
105
|
+
// `@gjsify//x` do not (no package name). A resolved disk path can never
|
|
106
|
+
// reach here — it is absolute or `./`-relative, so it never starts with the
|
|
107
|
+
// scope in the first place.
|
|
108
|
+
const rest = id.slice(WORKSPACE_SCOPE.length);
|
|
109
|
+
return rest.length > 0 && !rest.startsWith('/');
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Pure decision: does this import edge have to resolve, and to what?
|
|
113
|
+
*
|
|
114
|
+
* Split out from the plugin so the legitimate-vs-broken line is unit-testable
|
|
115
|
+
* without a bundler (`packages/infra/cli/src/unresolved-workspace-import.spec.ts`).
|
|
116
|
+
*/
|
|
117
|
+
export function classifyImport(input) {
|
|
118
|
+
const { source, importer, aliases, isExternal } = input;
|
|
119
|
+
// Entry modules are files the caller named; they are resolved by the
|
|
120
|
+
// orchestrator, never externalised.
|
|
121
|
+
if (importer === undefined)
|
|
122
|
+
return { verdict: 'ignore', reason: 'entry' };
|
|
123
|
+
// Relative / absolute / synthetic ids are not workspace edges.
|
|
124
|
+
if (source.startsWith('.') || source.startsWith('/') || source.startsWith('\0')) {
|
|
125
|
+
return { verdict: 'ignore', reason: 'not-bare' };
|
|
126
|
+
}
|
|
127
|
+
// A specifier the target DECLARES external is a deliberate external.
|
|
128
|
+
if (isExternal?.(source))
|
|
129
|
+
return { verdict: 'ignore', reason: 'declared-external' };
|
|
130
|
+
const aliasTarget = aliases?.[source];
|
|
131
|
+
const candidate = aliasTarget ?? source;
|
|
132
|
+
// The alias target may itself be declared external (`system` →
|
|
133
|
+
// `@gjsify/node-gi/system` on the node target).
|
|
134
|
+
if (candidate !== source && isExternal?.(candidate)) {
|
|
135
|
+
return { verdict: 'ignore', reason: 'declared-external' };
|
|
136
|
+
}
|
|
137
|
+
if (!isWorkspaceSpecifier(candidate))
|
|
138
|
+
return { verdict: 'ignore', reason: 'out-of-scope' };
|
|
139
|
+
return aliasTarget === undefined ? { verdict: 'check', candidate } : { verdict: 'check', candidate, aliasTarget };
|
|
140
|
+
}
|
|
141
|
+
/** A `@gjsify/*` edge the build promised to substitute could not be resolved. */
|
|
142
|
+
export class UnresolvedWorkspaceImportError extends Error {
|
|
143
|
+
details;
|
|
144
|
+
constructor(details) {
|
|
145
|
+
super(formatUnresolvedWorkspaceImport(details));
|
|
146
|
+
this.name = 'UnresolvedWorkspaceImportError';
|
|
147
|
+
this.details = details;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The actionable message. Names the specifier, the importer and the likely
|
|
152
|
+
* cause — the three things `UNRESOLVED_IMPORT` (and the post-hoc `node:` guard)
|
|
153
|
+
* cannot tell you.
|
|
154
|
+
*/
|
|
155
|
+
export function formatUnresolvedWorkspaceImport(details) {
|
|
156
|
+
const { target, source, candidate, aliasTarget, aliasedFrom, importer } = details;
|
|
157
|
+
// The specifiers `candidate` was standing in for: `source` itself when IT
|
|
158
|
+
// carried the table entry, else whatever the reverse lookup recovered.
|
|
159
|
+
//
|
|
160
|
+
// The reverse lookup is only reported when its fan-in is SMALL. A shared
|
|
161
|
+
// sink inverts to a useless wall of text — `@gjsify/empty` is the browser
|
|
162
|
+
// target for 49 specifiers, and printing all of them says nothing about
|
|
163
|
+
// which one this importer wrote, while `@gjsify/fs` inverts to exactly
|
|
164
|
+
// `['fs','node:fs']` (the two spellings of one builtin), which is the
|
|
165
|
+
// information the reader needs. Above the threshold, fall back to naming
|
|
166
|
+
// the package alone rather than guessing.
|
|
167
|
+
const reversed = aliasedFrom ?? [];
|
|
168
|
+
const substitutedFor = aliasTarget === undefined ? (reversed.length <= MAX_REPORTED_ALIAS_SOURCES ? reversed : []) : [source];
|
|
169
|
+
const quoted = substitutedFor.map((s) => `\`${s}\``).join(', ');
|
|
170
|
+
const headline = substitutedFor.length === 0
|
|
171
|
+
? `gjsify build --app ${target}: cannot resolve the workspace package \`${candidate}\`.`
|
|
172
|
+
: `gjsify build --app ${target}: cannot resolve \`${candidate}\`, the \`--app ${target}\` ` +
|
|
173
|
+
`substitution for ${quoted}.`;
|
|
174
|
+
// The symptom is a property of the SPECIFIER that would survive, not of the
|
|
175
|
+
// target: only a `node:` specifier reaching stock GJS produces the URI-scheme
|
|
176
|
+
// abort; a bare `@gjsify/*` or `<pkg>/register` one produces `Module not
|
|
177
|
+
// found` (GJS's ESM loader has no node_modules walker and does not follow
|
|
178
|
+
// `exports` maps). Naming the wrong one sends the reader hunting the wrong bug.
|
|
179
|
+
const surviving = substitutedFor.length === 0 ? [source] : substitutedFor;
|
|
180
|
+
const symptom = target !== 'gjs'
|
|
181
|
+
? ''
|
|
182
|
+
: surviving.some((s) => s.startsWith('node:'))
|
|
183
|
+
? ' Stock GJS aborts the whole module graph at load: ' +
|
|
184
|
+
'"ImportError: Unsupported URI scheme for importing: node".'
|
|
185
|
+
: ' Stock GJS would fail at load with "Module not found" — its ESM loader has no node_modules ' +
|
|
186
|
+
'walker and does not follow `package.json#exports` for bare specifiers.';
|
|
187
|
+
const consequence = (substitutedFor.length === 0
|
|
188
|
+
? `Leaving it unresolved would silently externalise \`${source}\` into the bundle, which the ` +
|
|
189
|
+
`\`--app ${target}\` runtime cannot resolve.`
|
|
190
|
+
: `Leaving it unresolved would silently externalise the ORIGINAL ${substitutedFor.length === 1 ? 'specifier' : 'specifiers'} ${quoted} into the ` +
|
|
191
|
+
`bundle, defeating the substitution the \`--app ${target}\` target exists to perform.`) + symptom;
|
|
192
|
+
return [
|
|
193
|
+
headline,
|
|
194
|
+
` imported by: ${importer}`,
|
|
195
|
+
'',
|
|
196
|
+
consequence,
|
|
197
|
+
'',
|
|
198
|
+
'Likely causes:',
|
|
199
|
+
' - `node_modules` is missing or incomplete — run `gjsify install` (`--immutable` in CI).',
|
|
200
|
+
` - the workspace package is not built: its \`exports["."]\` names a \`lib/\` that does not exist —`,
|
|
201
|
+
' run `gjsify run build:infra`, or `gjsify workspace <pkg> build` for the one package.',
|
|
202
|
+
` - \`${candidate}\` is genuinely not a dependency of this project — add it.`,
|
|
203
|
+
].join('\n');
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Reverse the substitution table: alias TARGET → the specifiers that route to it.
|
|
207
|
+
*
|
|
208
|
+
* Built once per plugin instance (the tables run to ~150 entries per target, and
|
|
209
|
+
* the guard only consults this on the failure path).
|
|
210
|
+
*/
|
|
211
|
+
export function buildReverseAliasIndex(aliases) {
|
|
212
|
+
const index = new Map();
|
|
213
|
+
if (!aliases)
|
|
214
|
+
return index;
|
|
215
|
+
for (const [from, to] of Object.entries(aliases)) {
|
|
216
|
+
const existing = index.get(to);
|
|
217
|
+
if (existing)
|
|
218
|
+
existing.push(from);
|
|
219
|
+
else
|
|
220
|
+
index.set(to, [from]);
|
|
221
|
+
}
|
|
222
|
+
for (const list of index.values())
|
|
223
|
+
list.sort();
|
|
224
|
+
return index;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Refuse a build whose `@gjsify/*` substitution could not be resolved.
|
|
228
|
+
*
|
|
229
|
+
* `order: 'post'` so every deliberate claim — `aliasPlugin` (`pre`),
|
|
230
|
+
* `napiNodeAddonPlugin` (`pre`), `gjsGiNodePlugin` (`pre`),
|
|
231
|
+
* `gjsImportsEmptyPlugin`, `externalsPlugin` — gets first refusal. Only ids that
|
|
232
|
+
* NOTHING claimed reach this hook, i.e. exactly the ones headed for the
|
|
233
|
+
* unresolved-import fallback.
|
|
234
|
+
*
|
|
235
|
+
* The hook re-runs the full resolution chain via `this.resolve(…, { skipSelf })`
|
|
236
|
+
* — which is what Rolldown's builtin resolver would have done next — and returns
|
|
237
|
+
* its result when it succeeds, so a healthy build resolves the specifier exactly
|
|
238
|
+
* once and behaves identically. Only a genuine `null` throws.
|
|
239
|
+
*/
|
|
240
|
+
export function unresolvedWorkspaceImportPlugin(options) {
|
|
241
|
+
const { target, aliases, isExternal } = options;
|
|
242
|
+
// Re-entrancy belt: `skipSelf: true` is supposed to keep our own
|
|
243
|
+
// `this.resolve` from re-entering this hook, but a hang is a far worse
|
|
244
|
+
// failure than a missed check, so the in-flight candidate is tracked
|
|
245
|
+
// explicitly. Keyed by `candidate\0importer` — the same package legitimately
|
|
246
|
+
// resolves from many importers.
|
|
247
|
+
const inFlight = new Set();
|
|
248
|
+
// Alias TARGET → the specifiers that route to it. Only read on the failure
|
|
249
|
+
// path, to recover the `node:fs` behind a failed `@gjsify/fs`.
|
|
250
|
+
const reverseAliases = buildReverseAliasIndex(aliases);
|
|
251
|
+
// Successful resolutions, memoized per (candidate, importer DIRECTORY) —
|
|
252
|
+
// the pair node resolution actually depends on, so a hit is the answer the
|
|
253
|
+
// chain would have recomputed. Without it this hook was the single most
|
|
254
|
+
// expensive plugin in a CLI-bundle build (32% of plugin time): `@gjsify/*`
|
|
255
|
+
// edges repeat across hundreds of modules, and each miss re-runs the whole
|
|
256
|
+
// `pre`-order chain (alias, napi-addon, gi-node) for the same target. Same
|
|
257
|
+
// shape + rationale as `napiNodeAddonPlugin`'s `bareEntryCache` (#840).
|
|
258
|
+
// Only POSITIVE results are cached: a failure throws, so it is never hot,
|
|
259
|
+
// and caching it could outlive a package appearing mid-watch.
|
|
260
|
+
const resolvedCache = new Map();
|
|
261
|
+
return {
|
|
262
|
+
name: 'gjsify-unresolved-workspace-import',
|
|
263
|
+
resolveId: {
|
|
264
|
+
order: 'post',
|
|
265
|
+
async handler(source, rawImporter, extraOptions) {
|
|
266
|
+
// The two engines disagree on "no importer": npm `rolldown`
|
|
267
|
+
// passes `undefined`, `@gjsify/rolldown-native` passes `null`
|
|
268
|
+
// (its hook payload round-trips through JSON, which has no
|
|
269
|
+
// `undefined`). `classifyImport` reads an absent importer as
|
|
270
|
+
// "this is an ENTRY, never externalised" via `=== undefined`,
|
|
271
|
+
// and `null` fails that test — so under the GJS bundler an
|
|
272
|
+
// entry module would have been resolved as a normal edge and
|
|
273
|
+
// could throw this plugin's error for a file the caller named
|
|
274
|
+
// explicitly. Normalise once, at the boundary — same fix as
|
|
275
|
+
// `napiNodeAddonPlugin` (#840), whose `dirname(null)` threw
|
|
276
|
+
// and took the whole GJS build down as an UNHANDLEABLE_ERROR.
|
|
277
|
+
const importer = typeof rawImporter === 'string' ? rawImporter : undefined;
|
|
278
|
+
if (extraOptions?.isEntry)
|
|
279
|
+
return null;
|
|
280
|
+
const verdict = classifyImport({ source, importer, aliases, isExternal });
|
|
281
|
+
if (verdict.verdict === 'ignore')
|
|
282
|
+
return null;
|
|
283
|
+
// `kind` participates in the key: it selects the `import`
|
|
284
|
+
// vs `require` export condition, so the same specifier can
|
|
285
|
+
// legitimately resolve to different files from one directory.
|
|
286
|
+
const key = `${verdict.candidate}\0${importer === undefined ? '' : dirname(importer)}\0${extraOptions?.kind ?? ''}`;
|
|
287
|
+
const cached = resolvedCache.get(key);
|
|
288
|
+
if (cached)
|
|
289
|
+
return cached;
|
|
290
|
+
if (inFlight.has(key))
|
|
291
|
+
return null;
|
|
292
|
+
inFlight.add(key);
|
|
293
|
+
let resolved;
|
|
294
|
+
try {
|
|
295
|
+
resolved = await this.resolve(verdict.candidate, importer, {
|
|
296
|
+
skipSelf: true,
|
|
297
|
+
kind: extraOptions?.kind,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
finally {
|
|
301
|
+
inFlight.delete(key);
|
|
302
|
+
}
|
|
303
|
+
if (resolved) {
|
|
304
|
+
resolvedCache.set(key, resolved);
|
|
305
|
+
return resolved;
|
|
306
|
+
}
|
|
307
|
+
throw new UnresolvedWorkspaceImportError({
|
|
308
|
+
target,
|
|
309
|
+
source,
|
|
310
|
+
candidate: verdict.candidate,
|
|
311
|
+
aliasTarget: verdict.aliasTarget,
|
|
312
|
+
aliasedFrom: reverseAliases.get(verdict.candidate)?.filter((s) => s !== source),
|
|
313
|
+
importer: importer ?? '<entry>',
|
|
314
|
+
});
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
}
|
|
@@ -319,7 +319,12 @@ export async function detectAutoGlobals(analysisOptions, pluginOptions, gjsifyPl
|
|
|
319
319
|
?.GJSIFY_DEBUG_AUTO_GLOBALS) {
|
|
320
320
|
const path = `/tmp/gjsify-auto-globals-failed-chunk-${i}.mjs`;
|
|
321
321
|
try {
|
|
322
|
-
//
|
|
322
|
+
// Dynamic `import()` on purpose: this is a debug-only
|
|
323
|
+
// branch and the module graph should not carry
|
|
324
|
+
// `node:fs` for it. (It is NOT a `require` — it never
|
|
325
|
+
// was; the rule-disable comment that used to sit here
|
|
326
|
+
// named a rule that cannot apply to an import
|
|
327
|
+
// expression.)
|
|
323
328
|
const fs = await import('node:fs');
|
|
324
329
|
fs.writeFileSync(path, code);
|
|
325
330
|
console.error(`[gjsify-auto-globals] parse failed on chunk #${i} — wrote ${path} for inspection`);
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// first, then re-exports the real entry. `\0`-prefixed ids are Rollup's
|
|
8
8
|
// convention for synthetic modules — Rolldown treats them as not-from-disk and
|
|
9
9
|
// skips the default loader.
|
|
10
|
+
import { GJSIFY_VIRTUAL_PREFIX } from './virtual-module-id.js';
|
|
10
11
|
/**
|
|
11
12
|
* If there are side-effect imports to land alongside the user's entry, wrap each
|
|
12
13
|
* entry in a virtual module that imports them first then re-exports the entry.
|
|
@@ -22,7 +23,7 @@ export function wrapInputWithSideEffects(input, sideEffects, opts = {}) {
|
|
|
22
23
|
return { input, plugin: null };
|
|
23
24
|
}
|
|
24
25
|
const userEntries = new Map(); // virtualId → realPath
|
|
25
|
-
const PREFIX =
|
|
26
|
+
const PREFIX = `${GJSIFY_VIRTUAL_PREFIX}entry:`;
|
|
26
27
|
function wrap(realPath) {
|
|
27
28
|
const id = PREFIX + realPath;
|
|
28
29
|
userEntries.set(id, realPath);
|
package/lib/utils/index.d.ts
CHANGED
|
@@ -6,3 +6,4 @@ export * from './merge.js';
|
|
|
6
6
|
export { detectFreeGlobals } from './detect-free-globals.js';
|
|
7
7
|
export { resolveGlobalsList, writeRegisterInjectFile } from './scan-globals.js';
|
|
8
8
|
export { inlineStaticReads } from './inline-static-reads.js';
|
|
9
|
+
export { GJSIFY_VIRTUAL_PREFIX, isGjsifyVirtualModuleId } from './virtual-module-id.js';
|
package/lib/utils/index.js
CHANGED
|
@@ -6,3 +6,4 @@ export * from './merge.js';
|
|
|
6
6
|
export { detectFreeGlobals } from './detect-free-globals.js';
|
|
7
7
|
export { resolveGlobalsList, writeRegisterInjectFile } from './scan-globals.js';
|
|
8
8
|
export { inlineStaticReads } from './inline-static-reads.js';
|
|
9
|
+
export { GJSIFY_VIRTUAL_PREFIX, isGjsifyVirtualModuleId } from './virtual-module-id.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Shared prefix of every gjsify-generated (synthetic) module id. */
|
|
2
|
+
export declare const GJSIFY_VIRTUAL_PREFIX = "\0gjsify-";
|
|
3
|
+
/**
|
|
4
|
+
* Is `id` a module gjsify's own plugins generated (as opposed to a user/npm
|
|
5
|
+
* source file)?
|
|
6
|
+
*
|
|
7
|
+
* Used by `aliasPlugin` to scope the alias tables to REAL source: generated
|
|
8
|
+
* module bodies name the runtime module they need explicitly and must not be
|
|
9
|
+
* re-pointed by a user alias or a slot-routing entry.
|
|
10
|
+
*
|
|
11
|
+
* @param id A Rolldown module id — typically a `resolveId` `importer`, which is
|
|
12
|
+
* `undefined` for entry modules.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isGjsifyVirtualModuleId(id: string | undefined | null): boolean;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// The ONE prefix every synthetic module id emitted by a gjsify plugin carries.
|
|
2
|
+
//
|
|
3
|
+
// Rollup/Rolldown convention is that a `\0`-prefixed id is not-from-disk; every
|
|
4
|
+
// gjsify plugin that emits one additionally namespaces it with `gjsify-`
|
|
5
|
+
// (`\0gjsify-entry:`, `\0gjsify-gi-node:`, `\0gjsify-napi-addon:`,
|
|
6
|
+
// `\0gjsify-empty-gjs-import`). Deriving those ids from THIS constant turns that
|
|
7
|
+
// naming habit into an enforced invariant, which matters because the alias layer
|
|
8
|
+
// keys off it: a module whose SOURCE we generated must bind to the RUNTIME's real
|
|
9
|
+
// modules, never to whatever a user `--alias` retargets a specifier onto.
|
|
10
|
+
//
|
|
11
|
+
// Concretely (the bug that motivated this): `gjsGiNodePlugin`'s virtual module
|
|
12
|
+
// emits `import { createRequire } from 'node:module'`. Under the node-gi consumer
|
|
13
|
+
// harness — which builds `--alias node:module=@gjsify/module` to put the polyfill
|
|
14
|
+
// UNDER TEST behind the specifier — that import was rewritten onto the polyfill
|
|
15
|
+
// itself, so the bundle called `@gjsify/module`'s `createRequire` at top level
|
|
16
|
+
// before its lazy GLib namespace proxy existed (`TypeError: Cannot read
|
|
17
|
+
// properties of undefined (reading 'filename_from_uri')`), and past that hit a
|
|
18
|
+
// genuine cycle: the polyfill's Gio-backed CJS loader needs GLib, and GLib is
|
|
19
|
+
// reached through `@gjsify/node-gi/gi` — which is what the virtual module was
|
|
20
|
+
// loading in the first place.
|
|
21
|
+
/** Shared prefix of every gjsify-generated (synthetic) module id. */
|
|
22
|
+
export const GJSIFY_VIRTUAL_PREFIX = '\0gjsify-';
|
|
23
|
+
/**
|
|
24
|
+
* Is `id` a module gjsify's own plugins generated (as opposed to a user/npm
|
|
25
|
+
* source file)?
|
|
26
|
+
*
|
|
27
|
+
* Used by `aliasPlugin` to scope the alias tables to REAL source: generated
|
|
28
|
+
* module bodies name the runtime module they need explicitly and must not be
|
|
29
|
+
* re-pointed by a user alias or a slot-routing entry.
|
|
30
|
+
*
|
|
31
|
+
* @param id A Rolldown module id — typically a `resolveId` `importer`, which is
|
|
32
|
+
* `undefined` for entry modules.
|
|
33
|
+
*/
|
|
34
|
+
export function isGjsifyVirtualModuleId(id) {
|
|
35
|
+
return typeof id === 'string' && id.startsWith(GJSIFY_VIRTUAL_PREFIX);
|
|
36
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/rolldown-plugin-gjsify",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.2",
|
|
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.24.2",
|
|
59
|
+
"@gjsify/resolve-npm": "^0.24.2",
|
|
60
|
+
"@gjsify/rolldown-plugin-deepkit": "^0.24.2",
|
|
61
|
+
"@gjsify/rolldown-plugin-pnp": "^0.24.2",
|
|
62
|
+
"@gjsify/vite-plugin-blueprint": "^0.24.2",
|
|
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.24.2",
|
|
72
72
|
"rolldown": "^1.1.4"
|
|
73
73
|
},
|
|
74
74
|
"peerDependenciesMeta": {
|