@gjsify/rolldown-plugin-gjsify 0.51.1 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/lib/app/browser.js +28 -0
- package/lib/app/gjs.js +14 -2
- package/lib/app/nativescript.js +15 -3
- package/lib/app/node.d.ts +1 -1
- package/lib/app/node.js +26 -10
- package/lib/index.d.ts +4 -2
- package/lib/index.js +4 -2
- package/lib/plugins/console-assign.d.ts +13 -0
- package/lib/plugins/console-assign.js +125 -0
- package/lib/plugins/css-as-string.js +126 -44
- package/lib/plugins/gi-renderer.d.ts +1 -1
- package/lib/plugins/gi-renderer.js +1 -1
- package/lib/plugins/gi-runtime-paths.js +5 -2
- package/lib/plugins/napi-node-addon.d.ts +48 -4
- package/lib/plugins/napi-node-addon.js +261 -85
- package/lib/plugins/node-native-external.d.ts +20 -0
- package/lib/plugins/node-native-external.js +145 -0
- package/lib/plugins/platform-resolve.d.ts +95 -5
- package/lib/plugins/platform-resolve.js +132 -11
- package/lib/plugins/rewrite-node-modules-paths.d.ts +18 -1
- package/lib/plugins/rewrite-node-modules-paths.js +181 -25
- package/lib/plugins/unresolved-workspace-import.js +89 -68
- package/lib/shims/addon-resolve.d.ts +19 -0
- package/lib/shims/addon-resolve.js +193 -0
- package/lib/utils/addon-platform.d.ts +23 -0
- package/lib/utils/addon-platform.js +50 -0
- package/lib/utils/auto-globals.d.ts +6 -0
- package/lib/utils/auto-globals.js +92 -15
- package/lib/utils/declare-build-input.d.ts +13 -0
- package/lib/utils/declare-build-input.js +37 -0
- package/lib/utils/detect-free-globals.d.ts +3 -0
- package/lib/utils/detect-free-globals.js +1 -1
- package/lib/utils/entry-wrapper.js +6 -1
- package/lib/utils/index.d.ts +1 -0
- package/lib/utils/index.js +1 -0
- package/lib/utils/inline-static-reads.d.ts +15 -1
- package/lib/utils/inline-static-reads.js +16 -8
- package/lib/utils/runtime.js +1 -1
- package/lib/utils/scan-globals.d.ts +2 -2
- package/lib/utils/scan-globals.js +20 -14
- package/lib/utils/zip-path.d.ts +9 -0
- package/lib/utils/zip-path.js +12 -0
- package/package.json +13 -9
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
// until 2026-08-22 — see `shouldInline` for the incident that measured the cost.
|
|
27
27
|
import { createRequire } from 'node:module';
|
|
28
28
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
29
|
-
import { inlineStaticReads } from '../utils/inline-static-reads.js';
|
|
29
|
+
import { inlineStaticReads, parseSource } from '../utils/inline-static-reads.js';
|
|
30
|
+
import { declareBuildInput } from '../utils/declare-build-input.js';
|
|
31
|
+
import { hasZipSegment } from '../utils/zip-path.js';
|
|
30
32
|
export const REWRITE_FILTER = /\.(m?js|cjs|[cm]?tsx?)$/;
|
|
31
33
|
const DIRNAME_DECL_RE = /(?:var|let|const)\s+__dirname\b|export\s+(?:var|let|const)\s+__dirname\b/;
|
|
32
34
|
const FILENAME_DECL_RE = /(?:var|let|const)\s+__filename\b|export\s+(?:var|let|const)\s+__filename\b/;
|
|
@@ -162,6 +164,132 @@ function needsDirnameDecl(src, flags) {
|
|
|
162
164
|
function needsFilenameDecl(src, flags) {
|
|
163
165
|
return flags.hasFilename && !FILENAME_DECL_RE.test(src);
|
|
164
166
|
}
|
|
167
|
+
// The local name the file-URL → path conversion is imported under. Prefixed so it
|
|
168
|
+
// cannot collide with a `fileURLToPath` the dep declares itself.
|
|
169
|
+
const URL_TO_PATH = '__gjsifyFileURLToPath';
|
|
170
|
+
/**
|
|
171
|
+
* A path expression for the file URL `url`, with `fileURLToPath` semantics.
|
|
172
|
+
*
|
|
173
|
+
* It used to be `new URL(…).pathname`, which is a URL PATH, not a filesystem path, and
|
|
174
|
+
* the two part ways twice: percent-escapes stay encoded (`/my%20app/x.js` on any OS),
|
|
175
|
+
* and on win32 the drive comes out as `/C:/…`, which no Windows API accepts. Every
|
|
176
|
+
* target has a `fileURLToPath` behind `node:url` (Node's own; `@gjsify/url` on GJS and
|
|
177
|
+
* in the browser), and the case-1 shim already relies on it.
|
|
178
|
+
*
|
|
179
|
+
* `dir` strips the trailing separator the directory URL leaves behind — either one,
|
|
180
|
+
* since win32 answers `\`.
|
|
181
|
+
*/
|
|
182
|
+
function urlToPathExpr(url, dir = false) {
|
|
183
|
+
const path = `${URL_TO_PATH}(${url})`;
|
|
184
|
+
return dir ? `${path}.replace(/[\\\\/]$/, "")` : path;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The import the {@link urlToPathExpr} expressions need, when any of them survive.
|
|
188
|
+
*
|
|
189
|
+
* A non-empty `preamble` is NOT the condition. When a file declares its OWN `__dirname`,
|
|
190
|
+
* {@link routeMetaPaths} declines the declaration and hands back the raw expression, so
|
|
191
|
+
* `import.meta.dirname` is still rewritten to a `__gjsifyFileURLToPath(…)` call while the
|
|
192
|
+
* preamble stays EMPTY — and a header keyed on `preamble.length` leaves that call bound to
|
|
193
|
+
* nothing. Measured: a node_modules dep with `var __dirname = "MINE"` beside
|
|
194
|
+
* `import.meta.dirname` emitted a bare `__gjsifyFileURLToPath(…)` and died on the
|
|
195
|
+
* ReferenceError at first use. `substitutes` is the same `import.meta` test the callers
|
|
196
|
+
* already branch on, so the header tracks what the output actually references.
|
|
197
|
+
*/
|
|
198
|
+
function urlToPathHeader(preamble, substitutes) {
|
|
199
|
+
return preamble.length > 0 || substitutes
|
|
200
|
+
? `import { fileURLToPath as ${URL_TO_PATH} } from "node:url";`
|
|
201
|
+
: undefined;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Route `import.meta.dirname`/`filename` the way the caller routes a bare
|
|
205
|
+
* `__dirname`/`__filename`: ONE `var` in the preamble, every occurrence referencing it.
|
|
206
|
+
* Answering them at all is the point — a bundled dep's location tokens must never
|
|
207
|
+
* resolve to the BUNDLE: @signalapp/libsignal-client's
|
|
208
|
+
* `node-gyp-build(import.meta.dirname + '/..')` looked beside the bundle and threw
|
|
209
|
+
* "No native build was found". (The addon package itself stays external now; a
|
|
210
|
+
* NON-addon dep spelling `import.meta.dirname` still lands here.) Case 1 answers inline
|
|
211
|
+
* through the runtime shim instead, and a file that declares its OWN `__dirname` keeps
|
|
212
|
+
* the inline expression, so a generated declaration can never collide with it.
|
|
213
|
+
*/
|
|
214
|
+
function routeMetaPaths(src, flags, decl) {
|
|
215
|
+
const declareDirname = (flags.hasDirname || /\bimport\.meta\.dirname\b/.test(src)) && !DIRNAME_DECL_RE.test(src);
|
|
216
|
+
const declareFilename = (flags.hasFilename || /\bimport\.meta\.filename\b/.test(src)) && !FILENAME_DECL_RE.test(src);
|
|
217
|
+
return {
|
|
218
|
+
preamble: [
|
|
219
|
+
...(declareDirname ? [`var __dirname = ${decl.dirname};`] : []),
|
|
220
|
+
...(declareFilename ? [`var __filename = ${decl.filename};`] : []),
|
|
221
|
+
],
|
|
222
|
+
dirname: declareDirname ? '__dirname' : decl.dirname,
|
|
223
|
+
filename: declareFilename ? '__filename' : decl.filename,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
const IMPORT_META_PROPS = ['url', 'dirname', 'filename'];
|
|
227
|
+
const IMPORT_META_RE = /\bimport\.meta\.(url|dirname|filename)\b/g;
|
|
228
|
+
/** Every `import.meta.<url|dirname|filename>` EXPRESSION in `src`, or null when it does not parse. */
|
|
229
|
+
function importMetaRanges(src, path) {
|
|
230
|
+
let ast;
|
|
231
|
+
try {
|
|
232
|
+
ast = parseSource(src, path);
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
// acorn trails the bundler on new syntax; the caller keeps the token rewrite.
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
const ranges = [];
|
|
239
|
+
const stack = [ast];
|
|
240
|
+
while (stack.length > 0) {
|
|
241
|
+
const node = stack.pop();
|
|
242
|
+
if (node === null || typeof node !== 'object')
|
|
243
|
+
continue;
|
|
244
|
+
if (Array.isArray(node)) {
|
|
245
|
+
stack.push(...node);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const object = node.object;
|
|
249
|
+
const property = node.property;
|
|
250
|
+
if (node.type === 'MemberExpression' &&
|
|
251
|
+
!node.computed &&
|
|
252
|
+
object?.type === 'MetaProperty' &&
|
|
253
|
+
object.meta?.name === 'import' &&
|
|
254
|
+
object.property?.name === 'meta' &&
|
|
255
|
+
property?.type === 'Identifier' &&
|
|
256
|
+
IMPORT_META_PROPS.includes(property.name ?? '')) {
|
|
257
|
+
ranges.push({
|
|
258
|
+
start: node.start,
|
|
259
|
+
end: node.end,
|
|
260
|
+
prop: property.name,
|
|
261
|
+
});
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
for (const value of Object.values(node))
|
|
265
|
+
if (value !== null && typeof value === 'object')
|
|
266
|
+
stack.push(value);
|
|
267
|
+
}
|
|
268
|
+
return ranges;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Replace every `import.meta.url` / `.dirname` / `.filename` expression in `src`.
|
|
272
|
+
*
|
|
273
|
+
* On the AST, not the text: the token also occurs inside STRINGS, and a text rewrite
|
|
274
|
+
* put a quoted replacement inside a quoted key — vite's `define: { "import.meta.url":
|
|
275
|
+
* … }` became `"__gjsifyModuleUrl("vite/…")"`, a PARSE_ERROR ("Expected `:` but found
|
|
276
|
+
* `Identifier`") that failed every `--app gjs` build reaching vite or wxt. A source
|
|
277
|
+
* acorn cannot parse keeps the token rewrite it always had.
|
|
278
|
+
*
|
|
279
|
+
* `dirname` and `filename` (Node ≥ 20.11) are answered too: GJS defines neither, so
|
|
280
|
+
* unplugin's `path.resolve(import.meta.dirname, …)` threw `The "path" argument must be
|
|
281
|
+
* of type string. Received type undefined` at load — where wxt's rebuild stopped next.
|
|
282
|
+
*/
|
|
283
|
+
export function replaceImportMeta(src, path, replacements) {
|
|
284
|
+
const ranges = importMetaRanges(src, path);
|
|
285
|
+
if (ranges === null)
|
|
286
|
+
return src.replace(IMPORT_META_RE, (_m, prop) => replacements[prop]);
|
|
287
|
+
let out = src;
|
|
288
|
+
for (const { start, end, prop } of ranges.sort((a, b) => b.start - a.start)) {
|
|
289
|
+
out = out.slice(0, start) + replacements[prop] + out.slice(end);
|
|
290
|
+
}
|
|
291
|
+
return out;
|
|
292
|
+
}
|
|
165
293
|
/** Prepend preamble + (optional) shim import to the source. */
|
|
166
294
|
function withPreamble(src, lines, importHeader) {
|
|
167
295
|
const parts = importHeader ? [importHeader, ...lines, src] : [...lines, src];
|
|
@@ -185,7 +313,15 @@ function rewriteOnDiskEsm(src, path, flags) {
|
|
|
185
313
|
preamble.push(`var __filename = __gjsifyModuleFile(${spec});`);
|
|
186
314
|
used.push('__gjsifyModuleFile');
|
|
187
315
|
}
|
|
188
|
-
|
|
316
|
+
if (src.includes('import.meta.dirname') && !used.includes('__gjsifyModuleDir'))
|
|
317
|
+
used.push('__gjsifyModuleDir');
|
|
318
|
+
if (src.includes('import.meta.filename') && !used.includes('__gjsifyModuleFile'))
|
|
319
|
+
used.push('__gjsifyModuleFile');
|
|
320
|
+
const code = replaceImportMeta(src, path, {
|
|
321
|
+
url: `__gjsifyModuleUrl(${spec})`,
|
|
322
|
+
dirname: `__gjsifyModuleDir(${spec})`,
|
|
323
|
+
filename: `__gjsifyModuleFile(${spec})`,
|
|
324
|
+
});
|
|
189
325
|
const header = `import { ${used.join(', ')} } from ${JSON.stringify(MODULE_RESOLVE_SHIM)};`;
|
|
190
326
|
return { code: withPreamble(code, preamble, header), moduleType: moduleTypeForPath(path) };
|
|
191
327
|
}
|
|
@@ -197,29 +333,40 @@ function rewriteOnDiskEsm(src, path, flags) {
|
|
|
197
333
|
function rewriteOnDiskEsmLegacy(src, path, bundleDir, flags) {
|
|
198
334
|
const relPath = relative(bundleDir, path);
|
|
199
335
|
const relDirWithSlash = (relative(bundleDir, dirname(path)) || '.') + '/';
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
336
|
+
const route = routeMetaPaths(src, flags, {
|
|
337
|
+
dirname: urlToPathExpr(`new URL(${JSON.stringify(relDirWithSlash)}, import.meta.url)`, true),
|
|
338
|
+
filename: urlToPathExpr(`new URL(${JSON.stringify(relPath)}, import.meta.url)`),
|
|
339
|
+
});
|
|
340
|
+
const code = replaceImportMeta(src, path, {
|
|
341
|
+
url: `new URL(${JSON.stringify(relPath)}, import.meta.url).href`,
|
|
342
|
+
dirname: route.dirname,
|
|
343
|
+
filename: route.filename,
|
|
344
|
+
});
|
|
345
|
+
const substitutes = /\bimport\.meta\.(?:dirname|filename)\b/.test(src);
|
|
346
|
+
return {
|
|
347
|
+
code: withPreamble(code, route.preamble, urlToPathHeader(route.preamble, substitutes)),
|
|
348
|
+
moduleType: moduleTypeForPath(path),
|
|
349
|
+
};
|
|
209
350
|
}
|
|
210
351
|
/**
|
|
211
352
|
* Case 3 — PnP zip-resident. Keep `import.meta.url` as the bundle's own URL and
|
|
212
353
|
* derive `__dirname`/`__filename` from it.
|
|
213
354
|
*/
|
|
214
355
|
function rewriteZipResident(src, path, flags) {
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
356
|
+
const route = routeMetaPaths(src, flags, {
|
|
357
|
+
dirname: urlToPathExpr('new URL(".", import.meta.url)', true),
|
|
358
|
+
filename: urlToPathExpr('import.meta.url'),
|
|
359
|
+
});
|
|
360
|
+
// `import.meta.url` keeps case 3's rule (the bundle's own URL); only the two
|
|
361
|
+
// members GJS does not define are routed through the declarations.
|
|
362
|
+
const substitutes = /\bimport\.meta\.(?:dirname|filename)\b/.test(src);
|
|
363
|
+
const code = substitutes
|
|
364
|
+
? replaceImportMeta(src, path, { url: 'import.meta.url', dirname: route.dirname, filename: route.filename })
|
|
365
|
+
: src;
|
|
366
|
+
return {
|
|
367
|
+
code: withPreamble(code, route.preamble, urlToPathHeader(route.preamble, substitutes)),
|
|
368
|
+
moduleType: moduleTypeForPath(path),
|
|
369
|
+
};
|
|
223
370
|
}
|
|
224
371
|
/**
|
|
225
372
|
* Case 4a — CJS, runtime-resolve. `__dirname`/`__filename` come from the module-resolve
|
|
@@ -269,13 +416,16 @@ function rewriteCjsAbsolute(src, path, flags) {
|
|
|
269
416
|
* Pure rewriter: the rewritten code plus the module type to re-parse it with, or `null`
|
|
270
417
|
* when the file references none of the tokens.
|
|
271
418
|
*/
|
|
272
|
-
export function rewriteContents(args, srcInput, bundleDir, runtimeResolve) {
|
|
419
|
+
export function rewriteContents(args, srcInput, bundleDir, runtimeResolve, declare) {
|
|
273
420
|
if (!shouldRewrite(args.path))
|
|
274
421
|
return null;
|
|
275
|
-
const inlined = inlineStaticReads(srcInput, args.path);
|
|
422
|
+
const inlined = inlineStaticReads(srcInput, args.path, declare);
|
|
276
423
|
const src = inlined.contents;
|
|
277
424
|
const flags = {
|
|
278
|
-
|
|
425
|
+
// A file that spelled ANY `import.meta.*` location is ESM, so it keeps the ESM
|
|
426
|
+
// strategies even when `import.meta.url` itself never appeared. The strategies
|
|
427
|
+
// answer all three on the AST (see `replaceImportMeta`), never as text.
|
|
428
|
+
hasMetaUrl: /\bimport\.meta\.(?:url|dirname|filename)\b/.test(src),
|
|
279
429
|
hasDirname: src.includes('__dirname'),
|
|
280
430
|
hasFilename: src.includes('__filename'),
|
|
281
431
|
};
|
|
@@ -285,7 +435,7 @@ export function rewriteContents(args, srcInput, bundleDir, runtimeResolve) {
|
|
|
285
435
|
}
|
|
286
436
|
// Step 2: dispatch by case (see file header).
|
|
287
437
|
if (flags.hasMetaUrl) {
|
|
288
|
-
if (relative(bundleDir, args.path)
|
|
438
|
+
if (hasZipSegment(relative(bundleDir, args.path))) {
|
|
289
439
|
return rewriteZipResident(src, args.path, flags);
|
|
290
440
|
}
|
|
291
441
|
return runtimeResolve
|
|
@@ -319,8 +469,14 @@ export function nodeModulesPathRewritePlugin(options) {
|
|
|
319
469
|
order: 'post',
|
|
320
470
|
filter: { id: REWRITE_FILTER },
|
|
321
471
|
handler(code, id) {
|
|
472
|
+
// What the inliner FOLDS into the bundle is an input of the
|
|
473
|
+
// output and no module graph names it, so it is declared through
|
|
474
|
+
// the standard contract — the same one `css-as-string` uses.
|
|
475
|
+
// Feature-detected, and the reason is measured: see
|
|
476
|
+
// `utils/declare-build-input.ts`.
|
|
477
|
+
const declare = (abs) => declareBuildInput(this, abs);
|
|
322
478
|
if (id.includes('node_modules')) {
|
|
323
|
-
const result = rewriteContents({ path: id }, code, options.bundleDir, runtimeResolve);
|
|
479
|
+
const result = rewriteContents({ path: id }, code, options.bundleDir, runtimeResolve, declare);
|
|
324
480
|
if (!result)
|
|
325
481
|
return null;
|
|
326
482
|
return { code: result.code, map: null };
|
|
@@ -330,7 +486,7 @@ export function nodeModulesPathRewritePlugin(options) {
|
|
|
330
486
|
// is the node_modules question this plugin was written for.
|
|
331
487
|
if (!shouldInline(id))
|
|
332
488
|
return null;
|
|
333
|
-
const inlined = inlineStaticReads(code, id);
|
|
489
|
+
const inlined = inlineStaticReads(code, id, declare);
|
|
334
490
|
return inlined.inlined > 0 ? { code: inlined.contents, map: null } : null;
|
|
335
491
|
},
|
|
336
492
|
},
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
// on the old fall-through instead of promoting a user's own mapping mistake to a
|
|
21
21
|
// fatal error in a plugin they did not ask for.
|
|
22
22
|
import { dirname } from 'node:path';
|
|
23
|
+
/** Marks this plugin's own `this.resolve` probes (`custom` resolve option). */
|
|
24
|
+
const OWN_PROBE = 'gjsify-unresolved-workspace-import/probe';
|
|
23
25
|
/** Bare `@gjsify/*` package specifier — the workspace edge this guard protects. */
|
|
24
26
|
const WORKSPACE_SCOPE = '@gjsify/';
|
|
25
27
|
/**
|
|
@@ -169,10 +171,8 @@ function describeResolverFailure(err) {
|
|
|
169
171
|
*/
|
|
170
172
|
export function unresolvedWorkspaceImportPlugin(options) {
|
|
171
173
|
const { target, aliases, isExternal, toolchainAnchor } = options;
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
// `candidate\0importer` — the same package legitimately resolves from many.
|
|
175
|
-
const inFlight = new Set();
|
|
174
|
+
// Resolutions in flight, by the cache key — concurrent askers await the same one.
|
|
175
|
+
const pending = new Map();
|
|
176
176
|
// Read only on the failure path, to recover the `node:fs` behind a failed
|
|
177
177
|
// `@gjsify/fs`.
|
|
178
178
|
const reverseAliases = buildReverseAliasIndex(aliases);
|
|
@@ -200,6 +200,12 @@ export function unresolvedWorkspaceImportPlugin(options) {
|
|
|
200
200
|
const importer = typeof rawImporter === 'string' ? rawImporter : undefined;
|
|
201
201
|
if (extraOptions?.isEntry)
|
|
202
202
|
return null;
|
|
203
|
+
// Re-entrancy belt: `skipSelf: true` should keep our own probes out of
|
|
204
|
+
// this hook, but one that got back in would now await ITSELF below — a
|
|
205
|
+
// hang, far worse than a missed check. The probes carry a marker, so the
|
|
206
|
+
// belt tells re-entrancy from a concurrent ask instead of guessing.
|
|
207
|
+
if (extraOptions?.custom?.[OWN_PROBE])
|
|
208
|
+
return null;
|
|
203
209
|
// The toolchain fallback below re-enters this hook with the anchor as
|
|
204
210
|
// the importer. Declining there keeps the probe from rescuing itself:
|
|
205
211
|
// the anchor cannot answer for the anchor, and the recursion has no
|
|
@@ -216,75 +222,90 @@ export function unresolvedWorkspaceImportPlugin(options) {
|
|
|
216
222
|
const cached = resolvedCache.get(key);
|
|
217
223
|
if (cached)
|
|
218
224
|
return cached;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
// neither specifier nor importer; swallowing it would report a real fault
|
|
233
|
-
// as the user's missing dependency. So it is caught, kept, and thrown
|
|
234
|
-
// BELOW with this plugin's context around it and the original as `cause`.
|
|
235
|
-
let resolverFailure;
|
|
225
|
+
// A CONCURRENT ask for the same key shares the one in flight. The belt
|
|
226
|
+
// used to answer it with `null`, as if it were re-entrancy, and Rolldown
|
|
227
|
+
// externalised the specifier: two modules of one directory, or two
|
|
228
|
+
// `import … from 'node:path'` statements of one module, resolving at the
|
|
229
|
+
// same moment. Measured twice — web-ext's `lib/cmd/*.js` (loaded in
|
|
230
|
+
// parallel through dynamic imports) lost the module-resolve shim, and
|
|
231
|
+
// vite's `chunks/node.js` kept a bare `node:path` — each only when the
|
|
232
|
+
// scheduler interleaved them, so the same build could pass or fail.
|
|
233
|
+
const shared = pending.get(key);
|
|
234
|
+
if (shared)
|
|
235
|
+
return shared;
|
|
236
|
+
const work = resolveCandidate.call(this, source, importer, verdict, extraOptions?.kind, key);
|
|
237
|
+
pending.set(key, work);
|
|
236
238
|
try {
|
|
237
|
-
|
|
238
|
-
skipSelf: true,
|
|
239
|
-
kind: extraOptions?.kind,
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
catch (err) {
|
|
243
|
-
resolverFailure = err;
|
|
239
|
+
return await work;
|
|
244
240
|
}
|
|
245
241
|
finally {
|
|
246
|
-
|
|
242
|
+
pending.delete(key);
|
|
247
243
|
}
|
|
248
|
-
if (resolved) {
|
|
249
|
-
resolvedCache.set(key, resolved);
|
|
250
|
-
return resolved;
|
|
251
|
-
}
|
|
252
|
-
// Toolchain fallback — see `toolchainAnchor`. Only after a real `null`:
|
|
253
|
-
// a resolver that ERRORED has not established that the project lacks the
|
|
254
|
-
// package, and rescuing there would turn "project first, always" into
|
|
255
|
-
// "project first unless the project errors".
|
|
256
|
-
if (resolverFailure === undefined && toolchainAnchor !== undefined) {
|
|
257
|
-
const fromToolchain = await this.resolve(verdict.candidate, toolchainAnchor, {
|
|
258
|
-
skipSelf: true,
|
|
259
|
-
kind: extraOptions?.kind,
|
|
260
|
-
});
|
|
261
|
-
if (fromToolchain) {
|
|
262
|
-
// The one visible trace that this artifact is MIXED — some of its
|
|
263
|
-
// `@gjsify/*` from the project, this one from the CLI's own
|
|
264
|
-
// install, possibly a different major. Without it a rescued build
|
|
265
|
-
// and a healthy one have byte-identical logs, and a wrong answer
|
|
266
|
-
// that leaves no trace is the expensive kind. Deduplicated by
|
|
267
|
-
// `resolvedCache`: one line per (candidate, importer dir, kind).
|
|
268
|
-
this.warn(`gjsify: \`${verdict.candidate}\` did not resolve from the project ` +
|
|
269
|
-
`(imported by ${importer ?? '<entry>'}) — using the copy installed beside the ` +
|
|
270
|
-
`running CLI (${toolchainAnchor}). Build the workspace package to use the ` +
|
|
271
|
-
`project's own copy.`);
|
|
272
|
-
resolvedCache.set(key, fromToolchain);
|
|
273
|
-
return fromToolchain;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
throw new UnresolvedWorkspaceImportError({
|
|
277
|
-
target,
|
|
278
|
-
source,
|
|
279
|
-
candidate: verdict.candidate,
|
|
280
|
-
aliasTarget: verdict.aliasTarget,
|
|
281
|
-
aliasedFrom: reverseAliases.get(verdict.candidate)?.filter((s) => s !== source),
|
|
282
|
-
importer: importer ?? '<entry>',
|
|
283
|
-
...(resolverFailure === undefined
|
|
284
|
-
? {}
|
|
285
|
-
: { resolverFailure: describeResolverFailure(resolverFailure) }),
|
|
286
|
-
}, resolverFailure);
|
|
287
244
|
},
|
|
288
245
|
},
|
|
289
246
|
};
|
|
247
|
+
async function resolveCandidate(source, importer, verdict, kind, key) {
|
|
248
|
+
let resolved = null;
|
|
249
|
+
// KEPT DELIBERATELY, and NOT as a miss. `this.resolve` re-runs the whole
|
|
250
|
+
// `pre`-order chain, so it can fail two very different ways. "Nothing is
|
|
251
|
+
// there" is `null` on both engines — npm `rolldown` always answered that,
|
|
252
|
+
// and `@gjsify/rolldown-native` now does too (`isResolveMiss`, which is
|
|
253
|
+
// where the engine mismatch that hid this plugin's diagnostic under GJS
|
|
254
|
+
// is fixed). What reaches this catch is the OTHER kind: a hook in that
|
|
255
|
+
// chain throwing on purpose (`napi-node-addon`'s `resolveAddonPath`), an
|
|
256
|
+
// EACCES, a corrupt `package.json`, a bridge fault. Letting it escape
|
|
257
|
+
// surfaces it as a bare "plugin `gjsify-alias` threw an error" naming
|
|
258
|
+
// neither specifier nor importer; swallowing it would report a real fault
|
|
259
|
+
// as the user's missing dependency. So it is caught, kept, and thrown
|
|
260
|
+
// BELOW with this plugin's context around it and the original as `cause`.
|
|
261
|
+
let resolverFailure;
|
|
262
|
+
try {
|
|
263
|
+
resolved = await this.resolve(verdict.candidate, importer, {
|
|
264
|
+
skipSelf: true,
|
|
265
|
+
kind,
|
|
266
|
+
custom: { [OWN_PROBE]: true },
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
catch (err) {
|
|
270
|
+
resolverFailure = err;
|
|
271
|
+
}
|
|
272
|
+
if (resolved) {
|
|
273
|
+
resolvedCache.set(key, resolved);
|
|
274
|
+
return resolved;
|
|
275
|
+
}
|
|
276
|
+
// Toolchain fallback — see `toolchainAnchor`. Only after a real `null`:
|
|
277
|
+
// a resolver that ERRORED has not established that the project lacks the
|
|
278
|
+
// package, and rescuing there would turn "project first, always" into
|
|
279
|
+
// "project first unless the project errors".
|
|
280
|
+
if (resolverFailure === undefined && toolchainAnchor !== undefined) {
|
|
281
|
+
const fromToolchain = await this.resolve(verdict.candidate, toolchainAnchor, {
|
|
282
|
+
skipSelf: true,
|
|
283
|
+
kind,
|
|
284
|
+
custom: { [OWN_PROBE]: true },
|
|
285
|
+
});
|
|
286
|
+
if (fromToolchain) {
|
|
287
|
+
// The one visible trace that this artifact is MIXED — some of its
|
|
288
|
+
// `@gjsify/*` from the project, this one from the CLI's own
|
|
289
|
+
// install, possibly a different major. Without it a rescued build
|
|
290
|
+
// and a healthy one have byte-identical logs, and a wrong answer
|
|
291
|
+
// that leaves no trace is the expensive kind. Deduplicated by
|
|
292
|
+
// `resolvedCache`: one line per (candidate, importer dir, kind).
|
|
293
|
+
this.warn(`gjsify: \`${verdict.candidate}\` did not resolve from the project ` +
|
|
294
|
+
`(imported by ${importer ?? '<entry>'}) — using the copy installed beside the ` +
|
|
295
|
+
`running CLI (${toolchainAnchor}). Build the workspace package to use the ` +
|
|
296
|
+
`project's own copy.`);
|
|
297
|
+
resolvedCache.set(key, fromToolchain);
|
|
298
|
+
return fromToolchain;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
throw new UnresolvedWorkspaceImportError({
|
|
302
|
+
target,
|
|
303
|
+
source,
|
|
304
|
+
candidate: verdict.candidate,
|
|
305
|
+
aliasTarget: verdict.aliasTarget,
|
|
306
|
+
aliasedFrom: reverseAliases.get(verdict.candidate)?.filter((s) => s !== source),
|
|
307
|
+
importer: importer ?? '<entry>',
|
|
308
|
+
...(resolverFailure === undefined ? {} : { resolverFailure: describeResolverFailure(resolverFailure) }),
|
|
309
|
+
}, resolverFailure);
|
|
310
|
+
}
|
|
290
311
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the addon's `.node` for the RUNNING host from the build-time table.
|
|
3
|
+
*
|
|
4
|
+
* `targets` maps platform keys (`linux-x64`, `linux-x64-musl`, …) to
|
|
5
|
+
* `<package>/<subpath>` specs. The host keys are tried most-specific first
|
|
6
|
+
* (exact, then libc-agnostic, then `*`). The package root is resolved at run
|
|
7
|
+
* time through the bundle-URL anchor, which is what makes the bundle relocatable
|
|
8
|
+
* (ADR 0084).
|
|
9
|
+
*
|
|
10
|
+
* Throws when no entry matches the running host, when the table is empty, and
|
|
11
|
+
* when the addon package is not installed next to the bundle. The last one is
|
|
12
|
+
* a LIMIT, not an oversight: identity-based resolution needs the package to be
|
|
13
|
+
* somewhere on disk, so a bundle shipped with no `node_modules` around it cannot
|
|
14
|
+
* load a third-party addon. It throws rather than returning
|
|
15
|
+
* `<bundle dir>/addons/…` because that directory is a declared layout nothing
|
|
16
|
+
* fills yet, and a path that cannot exist reaches `loadAddon` as a bare ENOENT
|
|
17
|
+
* that names neither the package nor the remedy. Both facts are in the message.
|
|
18
|
+
*/
|
|
19
|
+
export declare function __gjsifyAddonResolve(targets: Record<string, string>): string;
|