@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
package/lib/app/browser.js
CHANGED
|
@@ -4,10 +4,14 @@
|
|
|
4
4
|
// (they appear transitively via `@gjsify/unit` and similar packages with
|
|
5
5
|
// GJS-specific code paths). Bare Node specifiers + their `node:*` prefix
|
|
6
6
|
// variants are routed to `@gjsify/<X>` via the curated
|
|
7
|
-
// `ALIASES_NODE_FOR_BROWSER` table
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
7
|
+
// `ALIASES_NODE_FOR_BROWSER` table, whose VALUES already carry the
|
|
8
|
+
// per-runtimes-triplet slot routing (`@gjsify/<X>` → `@gjsify/<X>/browser` /
|
|
9
|
+
// `/globals` / `@gjsify/empty`): that table is exported through
|
|
10
|
+
// `withDerivedSlotRouting` in `@gjsify/resolve-npm`. Composition happens THERE,
|
|
11
|
+
// not as a resolver second pass here — `aliasPlugin` resolves an alias target
|
|
12
|
+
// in exactly one hop, because the map it receives is merged flat across tiers
|
|
13
|
+
// and a chain would re-route user `--alias` targets too (full argument in
|
|
14
|
+
// `withDerivedSlotRouting`'s doc comment and `plugins/alias.ts`).
|
|
11
15
|
import { aliasPlugin } from '../plugins/alias.js';
|
|
12
16
|
import { deepkitPlugin } from '@gjsify/rolldown-plugin-deepkit';
|
|
13
17
|
import blueprintPlugin from '@gjsify/vite-plugin-blueprint';
|
|
@@ -15,6 +19,7 @@ import { ALIASES_NODE_FOR_BROWSER, getDerivedAliasesSync } from '@gjsify/resolve
|
|
|
15
19
|
import { globToEntryPoints } from '../utils/entry-points.js';
|
|
16
20
|
import { gjsImportsEmptyPlugin } from '../plugins/gjs-imports-empty.js';
|
|
17
21
|
import { cssAsStringPlugin } from '../plugins/css-as-string.js';
|
|
22
|
+
import { unresolvedWorkspaceImportPlugin } from '../plugins/unresolved-workspace-import.js';
|
|
18
23
|
export const setupForBrowser = async (input) => {
|
|
19
24
|
const userExternal = input.userExternal ?? [];
|
|
20
25
|
const external = [...userExternal];
|
|
@@ -77,12 +82,26 @@ export const setupForBrowser = async (input) => {
|
|
|
77
82
|
},
|
|
78
83
|
treeshake: true,
|
|
79
84
|
};
|
|
85
|
+
// The substitution table exactly as `aliasPlugin` receives it — shared with
|
|
86
|
+
// the unresolved-workspace-import guard below so the guard reports the same
|
|
87
|
+
// promise the alias layer made.
|
|
88
|
+
const aliasEntries = flattenAliases(aliasMap);
|
|
80
89
|
const plugins = [
|
|
81
90
|
gjsImportsEmptyPlugin(),
|
|
82
|
-
aliasPlugin({ entries:
|
|
91
|
+
aliasPlugin({ entries: aliasEntries }),
|
|
83
92
|
blueprintPlugin(),
|
|
84
93
|
deepkitPlugin({ reflection: input.pluginOptions.reflection }),
|
|
85
94
|
cssAsStringPlugin(),
|
|
95
|
+
// `order: 'post'` — see app/gjs.ts. The browser target's whole job is to
|
|
96
|
+
// replace Node builtins with their `@gjsify/*` browser entries; when one
|
|
97
|
+
// of those cannot be resolved, Rolldown externalises the ORIGINAL bare
|
|
98
|
+
// specifier and a browser cannot resolve it at all. There is no
|
|
99
|
+
// `node:`-shaped emit-time symptom here, so this guard is the only signal.
|
|
100
|
+
unresolvedWorkspaceImportPlugin({
|
|
101
|
+
target: 'browser',
|
|
102
|
+
aliases: aliasEntries,
|
|
103
|
+
isExternal: (id) => external.includes(id),
|
|
104
|
+
}),
|
|
86
105
|
];
|
|
87
106
|
return { options, plugins };
|
|
88
107
|
};
|
package/lib/app/gjs.d.ts
CHANGED
|
@@ -55,6 +55,30 @@ export declare const setupForGjs: (input: GjsFactoryInput) => Promise<GjsBuildCo
|
|
|
55
55
|
* in `auto-globals.spec.ts` — canonical contract, change-detector status.
|
|
56
56
|
*/
|
|
57
57
|
export declare function createGjsExternalsPredicate(userExternal?: string[]): (id: string) => boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Recognize the shims this plugin INJECTS into a `--app gjs` bundle
|
|
60
|
+
* (`shims/module-resolve`, `shims/console-gjs`, `shims/unicorn-magic`, …), in
|
|
61
|
+
* both bare-specifier and resolved-disk-path form.
|
|
62
|
+
*
|
|
63
|
+
* They must be force-inlined for exactly the reason `/register` subpaths are,
|
|
64
|
+
* and the failure is the same one: GJS has no `require` and its ESM loader has
|
|
65
|
+
* neither a node_modules walker nor `exports`-map support, so an EXTERNALIZED
|
|
66
|
+
* shim aborts the program at load:
|
|
67
|
+
*
|
|
68
|
+
* Error: Calling `require` for
|
|
69
|
+
* "@gjsify/rolldown-plugin-gjsify/shims/module-resolve" in an environment
|
|
70
|
+
* that doesn't expose the `require` function
|
|
71
|
+
*
|
|
72
|
+
* The shim is not optional — the bundler injected it because the bundle needs
|
|
73
|
+
* it — so externalizing it can only ever produce an unloadable artifact. The
|
|
74
|
+
* carve-out short-circuits BEFORE the user-external check so `bundler.external`
|
|
75
|
+
* cannot override it, and it matches by SHAPE rather than an explicit list, so
|
|
76
|
+
* a shim added later is covered without touching this predicate.
|
|
77
|
+
*
|
|
78
|
+
* Scope is deliberately narrow: only OUR shim directory. A consumer package
|
|
79
|
+
* that happens to have a `shims/` folder is unaffected.
|
|
80
|
+
*/
|
|
81
|
+
export declare function isGjsifyShim(id: string): boolean;
|
|
58
82
|
/**
|
|
59
83
|
* Recognize the `/register` and `/register/<feature>` subpath shapes that
|
|
60
84
|
* `--globals auto` injects into the bundle as side-effect imports.
|
package/lib/app/gjs.js
CHANGED
|
@@ -10,9 +10,12 @@
|
|
|
10
10
|
// handled separately by `setupLib`.
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
12
|
import { dirname, resolve } from 'node:path';
|
|
13
|
+
import { existsSync } from 'node:fs';
|
|
14
|
+
import { createRequire } from 'node:module';
|
|
13
15
|
import { aliasPlugin } from '../plugins/alias.js';
|
|
14
16
|
import { externalsPlugin } from '../plugins/externals.js';
|
|
15
17
|
import { napiNodeAddonPlugin } from '../plugins/napi-node-addon.js';
|
|
18
|
+
import { unresolvedWorkspaceImportPlugin } from '../plugins/unresolved-workspace-import.js';
|
|
16
19
|
import { deepkitPlugin } from '@gjsify/rolldown-plugin-deepkit';
|
|
17
20
|
import blueprintPlugin from '@gjsify/vite-plugin-blueprint';
|
|
18
21
|
import { getAliasesForGjs } from '../utils/alias.js';
|
|
@@ -23,35 +26,38 @@ import { cssAsStringPlugin } from '../plugins/css-as-string.js';
|
|
|
23
26
|
import { shebangPlugin, resolveShebangLine, inputShebangStripPlugin } from '../plugins/shebang.js';
|
|
24
27
|
import { wrapInputWithSideEffects } from '../utils/entry-wrapper.js';
|
|
25
28
|
const _shimDir = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
/**
|
|
30
|
+
* Resolve one of this package's bundled shims to an absolute path.
|
|
31
|
+
*
|
|
32
|
+
* `createRequire` is a NAMED IMPORT from `node:module`, never a bare
|
|
33
|
+
* `require(...)` — this source is ESM (see AGENTS.md "Our source is ESM —
|
|
34
|
+
* no bare `require`"). It is used here for its RESOLVER, which is
|
|
35
|
+
* `exports`-map aware, and only to answer "where does this subpath live";
|
|
36
|
+
* nothing is loaded through it.
|
|
37
|
+
*/
|
|
26
38
|
function resolveShim(shimName) {
|
|
27
39
|
// Preferred: relative to this module's directory. Works under the
|
|
28
40
|
// normal Node consumer flow where `_shimDir` = `<pkg>/lib/app/`.
|
|
29
41
|
const relative = resolve(_shimDir, `../shims/${shimName}.js`);
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
33
|
-
fs = require('node:fs');
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
return relative;
|
|
37
|
-
}
|
|
38
|
-
if (fs.existsSync(relative))
|
|
42
|
+
if (existsSync(relative))
|
|
39
43
|
return relative;
|
|
40
44
|
// Fallback: when the orchestrator is bundled into a single .mjs
|
|
41
45
|
// (GJS-CLI self-host loop) `_shimDir` collapses to the bundle's
|
|
42
|
-
// own directory and the relative lookup misses.
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
// subpath export in package.json for this fallback to resolve.
|
|
46
|
+
// own directory and the relative lookup misses. The published subpath
|
|
47
|
+
// export `./shims/<name>` resolves under both Node and GJS without
|
|
48
|
+
// further walking. Each shim MUST be a `./shims/<name>` subpath export
|
|
49
|
+
// in package.json for this fallback to resolve.
|
|
47
50
|
try {
|
|
48
|
-
|
|
49
|
-
const Module = require('node:module');
|
|
50
|
-
const require_ = Module.createRequire(import.meta.url);
|
|
51
|
-
return require_.resolve(`@gjsify/rolldown-plugin-gjsify/shims/${shimName}`);
|
|
51
|
+
return createRequire(import.meta.url).resolve(`@gjsify/rolldown-plugin-gjsify/shims/${shimName}`);
|
|
52
52
|
}
|
|
53
|
-
catch {
|
|
54
|
-
|
|
53
|
+
catch (cause) {
|
|
54
|
+
// Both lookups failed — the shim is genuinely absent. Returning the
|
|
55
|
+
// unverified `relative` here would hand a non-existent path to the
|
|
56
|
+
// alias map, and the failure would resurface much later as an
|
|
57
|
+
// unrelated "could not resolve" naming a path nobody wrote. Name the
|
|
58
|
+
// actual problem, at the site that knows what it was looking for.
|
|
59
|
+
throw new Error(`@gjsify/rolldown-plugin-gjsify: cannot locate the "${shimName}" shim. ` +
|
|
60
|
+
`Tried ${relative} and the "./shims/${shimName}" subpath export.`, { cause });
|
|
55
61
|
}
|
|
56
62
|
}
|
|
57
63
|
export const setupForGjs = async (input) => {
|
|
@@ -72,7 +78,12 @@ export const setupForGjs = async (input) => {
|
|
|
72
78
|
//
|
|
73
79
|
// `exactExternal` filters out register subpaths so a user external
|
|
74
80
|
// entry can never override the force-inline carve-out below.
|
|
75
|
-
const exactExternal = [
|
|
81
|
+
const exactExternal = [
|
|
82
|
+
'cairo',
|
|
83
|
+
'gettext',
|
|
84
|
+
'system',
|
|
85
|
+
...userExternal.filter((id) => !isRegisterSubpath(id) && !isGjsifyShim(id)),
|
|
86
|
+
];
|
|
76
87
|
const external = createGjsExternalsPredicate(userExternal);
|
|
77
88
|
const format = input.pluginOptions.format ?? 'esm';
|
|
78
89
|
const exclude = input.pluginOptions.exclude ?? [];
|
|
@@ -178,6 +189,13 @@ export const setupForGjs = async (input) => {
|
|
|
178
189
|
treeshake: true,
|
|
179
190
|
};
|
|
180
191
|
const bundleDir = getBundleDirFromOutput(input.output);
|
|
192
|
+
// The substitution table exactly as `aliasPlugin` receives it — shared with
|
|
193
|
+
// the unresolved-workspace-import guard below so the guard reports the same
|
|
194
|
+
// promise the alias layer made.
|
|
195
|
+
const aliasEntries = {
|
|
196
|
+
'random-access-file': 'random-access-file/index.js',
|
|
197
|
+
...flattenAliases(aliasMap),
|
|
198
|
+
};
|
|
181
199
|
const plugins = [
|
|
182
200
|
// Virtual-entry plugin runs FIRST so its resolveId/load match the
|
|
183
201
|
// synthetic input ids that `wrapInputWithSideEffects` produces.
|
|
@@ -191,12 +209,7 @@ export const setupForGjs = async (input) => {
|
|
|
191
209
|
// random-access-file's 'browser' field maps to a throwing stub; force
|
|
192
210
|
// the fs-backed Node entry. Implemented via the gjsify alias plugin
|
|
193
211
|
// as a direct entry-table override.
|
|
194
|
-
aliasPlugin({
|
|
195
|
-
entries: {
|
|
196
|
-
'random-access-file': 'random-access-file/index.js',
|
|
197
|
-
...flattenAliases(aliasMap),
|
|
198
|
-
},
|
|
199
|
-
}),
|
|
212
|
+
aliasPlugin({ entries: aliasEntries }),
|
|
200
213
|
// Transparent N-API `.node`-addon loader — the forward mirror of
|
|
201
214
|
// `gjsGiNodePlugin`. Its `order:'pre'` resolveId claims a native-addon
|
|
202
215
|
// acquisition (`bindings`/`node-gyp-build`/a direct `.node`/a napi-rs
|
|
@@ -229,6 +242,14 @@ export const setupForGjs = async (input) => {
|
|
|
229
242
|
const line = resolveShebangLine(input.shebang);
|
|
230
243
|
return shebangPlugin({ enabled: line !== null, line: line ?? undefined });
|
|
231
244
|
})(),
|
|
245
|
+
// LAST claim on an id nothing else wanted (`order: 'post'`): a
|
|
246
|
+
// `@gjsify/*` substitution that cannot be resolved is a build ERROR, not
|
|
247
|
+
// a silent external. Without it Rolldown externalises the ORIGINAL
|
|
248
|
+
// specifier, exits 0, and writes a bundle stock GJS aborts on at load
|
|
249
|
+
// (`ImportError: Unsupported URI scheme for importing: node`) — the
|
|
250
|
+
// failure `utils/gjs-bundle-guard.ts` catches at emit time, caught here
|
|
251
|
+
// at its source instead, with the importer and the cause named.
|
|
252
|
+
unresolvedWorkspaceImportPlugin({ target: 'gjs', aliases: aliasEntries, isExternal: external }),
|
|
232
253
|
];
|
|
233
254
|
return { options, plugins };
|
|
234
255
|
};
|
|
@@ -271,7 +292,7 @@ function flattenAliases(map) {
|
|
|
271
292
|
export function createGjsExternalsPredicate(userExternal = []) {
|
|
272
293
|
const exact = ['cairo', 'gettext', 'system', ...userExternal];
|
|
273
294
|
return (id) => {
|
|
274
|
-
if (isRegisterSubpath(id))
|
|
295
|
+
if (isRegisterSubpath(id) || isGjsifyShim(id))
|
|
275
296
|
return false;
|
|
276
297
|
if (id.startsWith('gi://'))
|
|
277
298
|
return true;
|
|
@@ -280,6 +301,36 @@ export function createGjsExternalsPredicate(userExternal = []) {
|
|
|
280
301
|
return false;
|
|
281
302
|
};
|
|
282
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Recognize the shims this plugin INJECTS into a `--app gjs` bundle
|
|
306
|
+
* (`shims/module-resolve`, `shims/console-gjs`, `shims/unicorn-magic`, …), in
|
|
307
|
+
* both bare-specifier and resolved-disk-path form.
|
|
308
|
+
*
|
|
309
|
+
* They must be force-inlined for exactly the reason `/register` subpaths are,
|
|
310
|
+
* and the failure is the same one: GJS has no `require` and its ESM loader has
|
|
311
|
+
* neither a node_modules walker nor `exports`-map support, so an EXTERNALIZED
|
|
312
|
+
* shim aborts the program at load:
|
|
313
|
+
*
|
|
314
|
+
* Error: Calling `require` for
|
|
315
|
+
* "@gjsify/rolldown-plugin-gjsify/shims/module-resolve" in an environment
|
|
316
|
+
* that doesn't expose the `require` function
|
|
317
|
+
*
|
|
318
|
+
* The shim is not optional — the bundler injected it because the bundle needs
|
|
319
|
+
* it — so externalizing it can only ever produce an unloadable artifact. The
|
|
320
|
+
* carve-out short-circuits BEFORE the user-external check so `bundler.external`
|
|
321
|
+
* cannot override it, and it matches by SHAPE rather than an explicit list, so
|
|
322
|
+
* a shim added later is covered without touching this predicate.
|
|
323
|
+
*
|
|
324
|
+
* Scope is deliberately narrow: only OUR shim directory. A consumer package
|
|
325
|
+
* that happens to have a `shims/` folder is unaffected.
|
|
326
|
+
*/
|
|
327
|
+
export function isGjsifyShim(id) {
|
|
328
|
+
// Bare/fully-qualified: `@gjsify/rolldown-plugin-gjsify/shims/<name>`.
|
|
329
|
+
if (/@gjsify\/rolldown-plugin-gjsify\/shims\/[^?]+$/.test(id))
|
|
330
|
+
return true;
|
|
331
|
+
// Resolved disk path: `…/rolldown-plugin-gjsify/{lib,src}/shims/<name>.js`.
|
|
332
|
+
return /[\\/]rolldown-plugin-gjsify[\\/](?:lib|src)[\\/]shims[\\/]/.test(id);
|
|
333
|
+
}
|
|
283
334
|
/**
|
|
284
335
|
* Recognize the `/register` and `/register/<feature>` subpath shapes that
|
|
285
336
|
* `--globals auto` injects into the bundle as side-effect imports.
|
package/lib/app/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { setupForGjs, isRegisterSubpath, createGjsExternalsPredicate } from './gjs.js';
|
|
1
|
+
export { setupForGjs, isRegisterSubpath, isGjsifyShim, createGjsExternalsPredicate } from './gjs.js';
|
|
2
2
|
export type { GjsBuildConfig, GjsFactoryInput } from './gjs.js';
|
|
3
3
|
export { setupForNode, enableGjsRegistersForNode, isGjsSourceBuild } from './node.js';
|
|
4
4
|
export type { NodeBuildConfig, NodeFactoryInput } from './node.js';
|
package/lib/app/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { setupForGjs, isRegisterSubpath, createGjsExternalsPredicate } from './gjs.js';
|
|
1
|
+
export { setupForGjs, isRegisterSubpath, isGjsifyShim, createGjsExternalsPredicate } from './gjs.js';
|
|
2
2
|
export { setupForNode, enableGjsRegistersForNode, isGjsSourceBuild } from './node.js';
|
|
3
3
|
export { setupForBrowser } from './browser.js';
|
|
4
4
|
export { setupForNativescript } from './nativescript.js';
|
package/lib/app/nativescript.js
CHANGED
|
@@ -16,10 +16,21 @@
|
|
|
16
16
|
// GJS-specific code paths that never execute on NS.
|
|
17
17
|
//
|
|
18
18
|
// Bare Node specifiers + their `node:*` prefix variants are routed to
|
|
19
|
-
// `@gjsify/<X>` via the curated `ALIASES_NODE_FOR_NATIVESCRIPT` table
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
19
|
+
// `@gjsify/<X>` via the curated `ALIASES_NODE_FOR_NATIVESCRIPT` table. Unlike
|
|
20
|
+
// the browser table, this one is NOT passed through `withDerivedSlotRouting`:
|
|
21
|
+
// slot routing would only change 4 of its 122 entries and all four are
|
|
22
|
+
// regressions — `fs`/`fs/promises` (+ their `node:` forms) deliberately point
|
|
23
|
+
// at `@gjsify/native-fs-bridge`, which declares `nativescript: "native"` in the
|
|
24
|
+
// "this package IS the native implementation" sense and therefore routes to a
|
|
25
|
+
// `globals.mjs` it does not ship → `@gjsify/empty`. The slot vocabulary's
|
|
26
|
+
// `native` means "the RUNTIME provides this, use its value", so the bridge
|
|
27
|
+
// packages are mis-declared; that is tracked in STATUS.md and must be settled
|
|
28
|
+
// before this table is composed. A direct `import … from
|
|
29
|
+
// '@gjsify/native-fs-bridge'` already hits the derived map (plain key hit) and
|
|
30
|
+
// is emptied TODAY — same root cause, listed in the same TODO.
|
|
31
|
+
//
|
|
32
|
+
// Derived `@gjsify/<X>` entries are merged into the map below, so an import by
|
|
33
|
+
// PACKAGE NAME still routes per its declared slot in a single hop.
|
|
23
34
|
//
|
|
24
35
|
// No `cssAsStringPlugin` (NativeScript ships its own CSS pipeline as part
|
|
25
36
|
// of `@nativescript/core`) and no `blueprintPlugin` (Blueprint is GTK-only).
|
|
@@ -29,6 +40,7 @@ import { ALIASES_NODE_FOR_NATIVESCRIPT, getDerivedAliasesSync } from '@gjsify/re
|
|
|
29
40
|
import { globToEntryPoints } from '../utils/entry-points.js';
|
|
30
41
|
import { gjsImportsEmptyPlugin } from '../plugins/gjs-imports-empty.js';
|
|
31
42
|
import { platformResolvePlugin, detectNativescriptPlatform, nativescriptPlatformDefines, } from '../plugins/platform-resolve.js';
|
|
43
|
+
import { unresolvedWorkspaceImportPlugin } from '../plugins/unresolved-workspace-import.js';
|
|
32
44
|
export const setupForNativescript = async (input) => {
|
|
33
45
|
const userExternal = input.userExternal ?? [];
|
|
34
46
|
const external = [...userExternal];
|
|
@@ -105,18 +117,32 @@ export const setupForNativescript = async (input) => {
|
|
|
105
117
|
},
|
|
106
118
|
treeshake: true,
|
|
107
119
|
};
|
|
120
|
+
// The substitution table exactly as `aliasPlugin` receives it — shared with
|
|
121
|
+
// the unresolved-workspace-import guard below so the guard reports the same
|
|
122
|
+
// promise the alias layer made.
|
|
123
|
+
const aliasEntries = flattenAliases(aliasMap);
|
|
108
124
|
const plugins = [
|
|
109
125
|
gjsImportsEmptyPlugin(),
|
|
110
126
|
// Platform-specific source variants (`*.android` / `*.ios` /
|
|
111
127
|
// `*.native`) win over the base file — resolved BEFORE the Node-builtin
|
|
112
128
|
// alias routing so a platform fork of a portable module is honored.
|
|
113
129
|
platformResolvePlugin({ platform }),
|
|
114
|
-
aliasPlugin({ entries:
|
|
130
|
+
aliasPlugin({ entries: aliasEntries }),
|
|
115
131
|
// NO blueprintPlugin — Blueprint is a GTK-specific UI DSL
|
|
116
132
|
// NO cssAsStringPlugin — NS ships its own CSS pipeline via
|
|
117
133
|
// @nativescript/core; .css imports are handled by the consuming
|
|
118
134
|
// @nativescript/webpack or @nativescript/vite build
|
|
119
135
|
deepkitPlugin({ reflection: input.pluginOptions.reflection }),
|
|
136
|
+
// `order: 'post'` — see app/gjs.ts. Native-bridge identifiers
|
|
137
|
+
// (`java.*`, `NS*`, …) are ambient GLOBALS on this target, never
|
|
138
|
+
// imports, so they never reach this hook; only a `@gjsify/*` edge does,
|
|
139
|
+
// and on NS as everywhere else an unresolvable one is a broken bundle
|
|
140
|
+
// rather than something the runtime will provide.
|
|
141
|
+
unresolvedWorkspaceImportPlugin({
|
|
142
|
+
target: 'nativescript',
|
|
143
|
+
aliases: aliasEntries,
|
|
144
|
+
isExternal: (id) => external.includes(id),
|
|
145
|
+
}),
|
|
120
146
|
];
|
|
121
147
|
return { options, plugins };
|
|
122
148
|
};
|
package/lib/app/node.js
CHANGED
|
@@ -14,6 +14,7 @@ import { nodeModulesPathRewritePlugin, getBundleDirFromOutput } from '../plugins
|
|
|
14
14
|
import { cssAsStringPlugin } from '../plugins/css-as-string.js';
|
|
15
15
|
import { gjsImportsEmptyPlugin } from '../plugins/gjs-imports-empty.js';
|
|
16
16
|
import { gjsGiNodePlugin, gjsBuiltinModulesNodePlugin } from '../plugins/gjs-gi-node.js';
|
|
17
|
+
import { unresolvedWorkspaceImportPlugin } from '../plugins/unresolved-workspace-import.js';
|
|
17
18
|
import { wrapInputWithSideEffects } from '../utils/entry-wrapper.js';
|
|
18
19
|
/**
|
|
19
20
|
* Side-effect specifier the node target injects when GJS ambient globals
|
|
@@ -219,6 +220,10 @@ export const setupForNode = async (input) => {
|
|
|
219
220
|
...input.pluginOptions.aliases,
|
|
220
221
|
...input.userAliases,
|
|
221
222
|
};
|
|
223
|
+
// The substitution table exactly as `aliasPlugin` receives it — shared with
|
|
224
|
+
// the unresolved-workspace-import guard below so the guard reports the same
|
|
225
|
+
// promise the alias layer made.
|
|
226
|
+
const aliasEntries = flattenAliases(aliasMap);
|
|
222
227
|
const bundleDir = getBundleDirFromOutput(input.output);
|
|
223
228
|
// Rolldown's CJS interop wraps bundled CJS via `__commonJSMin` and
|
|
224
229
|
// routes external Node-builtin `require()` through `__require` —
|
|
@@ -317,7 +322,7 @@ export const setupForNode = async (input) => {
|
|
|
317
322
|
// `requireGi`, exactly like the ambient-globals case. Same predicate as
|
|
318
323
|
// the register-alias lift above — one question, one flag.
|
|
319
324
|
gjsImportsEmptyPlugin({ emptyGirs: !gjsSourceBuild }),
|
|
320
|
-
aliasPlugin({ entries:
|
|
325
|
+
aliasPlugin({ entries: aliasEntries }),
|
|
321
326
|
// Blueprint (.blp → XML string): the reverse bridge runs REAL GTK on
|
|
322
327
|
// Node, so a GJS app entry (composite-template windows) must build for
|
|
323
328
|
// `--app node` too. Only claims `.blp` files — inert for plain-Node
|
|
@@ -334,6 +339,13 @@ export const setupForNode = async (input) => {
|
|
|
334
339
|
// symptom). A plain-Node bundle (no node-gi) keeps the CSS pristine.
|
|
335
340
|
cssAsStringPlugin(input.pluginOptions.nodeGiGlobalsInject ? { targets: { firefox: 60 << 16 } } : {}),
|
|
336
341
|
nodeModulesPathRewritePlugin({ bundleDir }),
|
|
342
|
+
// `order: 'post'` — fires only for ids nothing above claimed, i.e. the
|
|
343
|
+
// ones headed for Rolldown's unresolved-import → external fallback. The
|
|
344
|
+
// node target has no `node:`-shaped symptom for the emit-time guard to
|
|
345
|
+
// detect (its `node:` builtins ARE external by policy), so this is the
|
|
346
|
+
// ONLY thing standing between an unbuilt `@gjsify/*` workspace edge and
|
|
347
|
+
// a bundle that re-imports the bare specifier at runtime.
|
|
348
|
+
unresolvedWorkspaceImportPlugin({ target: 'node', aliases: aliasEntries, isExternal: external }),
|
|
337
349
|
];
|
|
338
350
|
return { options, plugins };
|
|
339
351
|
};
|
package/lib/index.d.ts
CHANGED
|
@@ -11,10 +11,14 @@ export { textLoaderPlugin } from './plugins/text-loader.js';
|
|
|
11
11
|
export type { TextLoaderPluginOptions, LoaderKind } from './plugins/text-loader.js';
|
|
12
12
|
export { shebangPlugin, GJS_SHEBANG, NODE_SHEBANG, expandEnvTemplate, resolveShebangLine } from './plugins/shebang.js';
|
|
13
13
|
export type { ShebangPluginOptions } from './plugins/shebang.js';
|
|
14
|
+
export { aliasPlugin } from './plugins/alias.js';
|
|
15
|
+
export type { AliasPluginOptions } from './plugins/alias.js';
|
|
14
16
|
export { gjsImportsEmptyPlugin } from './plugins/gjs-imports-empty.js';
|
|
15
17
|
export { externalsPlugin } from './plugins/externals.js';
|
|
16
18
|
export type { ExternalsPredicate, ExternalsPluginOptions } from './plugins/externals.js';
|
|
17
|
-
export {
|
|
19
|
+
export { unresolvedWorkspaceImportPlugin, classifyImport, isWorkspaceSpecifier, formatUnresolvedWorkspaceImport, buildReverseAliasIndex, UnresolvedWorkspaceImportError, } from './plugins/unresolved-workspace-import.js';
|
|
20
|
+
export type { WorkspaceImportGuardOptions, WorkspaceImportGuardTarget, ImportVerdict, ClassifyImportInput, UnresolvedWorkspaceImportDetails, } from './plugins/unresolved-workspace-import.js';
|
|
21
|
+
export { napiNodeAddonPlugin, resolveAddonPath, nearestPackageRoot, classifySpecifier, directNodeShim, nodeGypBuildShim, bindingsShim, napiRsShim, ADDON_FILTER_RE, isNapiRsPackageJson, isNapiRsSibling, detectNapiRsEntry, AddonNotBuiltError, } from './plugins/napi-node-addon.js';
|
|
18
22
|
export type { NapiNodeAddonPluginOptions, AddonPackageJson } from './plugins/napi-node-addon.js';
|
|
19
23
|
export { platformResolvePlugin, detectNativescriptPlatform, nativescriptPlatformDefines, } from './plugins/platform-resolve.js';
|
|
20
24
|
export type { PlatformResolvePluginOptions, NativescriptPlatform } from './plugins/platform-resolve.js';
|
package/lib/index.js
CHANGED
|
@@ -8,9 +8,11 @@ export { processStubPlugin, GJS_PROCESS_STUB, composeBanner } from './plugins/pr
|
|
|
8
8
|
export { cssAsStringPlugin } from './plugins/css-as-string.js';
|
|
9
9
|
export { textLoaderPlugin } from './plugins/text-loader.js';
|
|
10
10
|
export { shebangPlugin, GJS_SHEBANG, NODE_SHEBANG, expandEnvTemplate, resolveShebangLine } from './plugins/shebang.js';
|
|
11
|
+
export { aliasPlugin } from './plugins/alias.js';
|
|
11
12
|
export { gjsImportsEmptyPlugin } from './plugins/gjs-imports-empty.js';
|
|
12
13
|
export { externalsPlugin } from './plugins/externals.js';
|
|
13
|
-
export {
|
|
14
|
+
export { unresolvedWorkspaceImportPlugin, classifyImport, isWorkspaceSpecifier, formatUnresolvedWorkspaceImport, buildReverseAliasIndex, UnresolvedWorkspaceImportError, } from './plugins/unresolved-workspace-import.js';
|
|
15
|
+
export { napiNodeAddonPlugin, resolveAddonPath, nearestPackageRoot, classifySpecifier, directNodeShim, nodeGypBuildShim, bindingsShim, napiRsShim, ADDON_FILTER_RE, isNapiRsPackageJson, isNapiRsSibling, detectNapiRsEntry, AddonNotBuiltError, } from './plugins/napi-node-addon.js';
|
|
14
16
|
export { platformResolvePlugin, detectNativescriptPlatform, nativescriptPlatformDefines, } from './plugins/platform-resolve.js';
|
|
15
17
|
export * from './plugin.js';
|
|
16
18
|
import { gjsifyPlugin } from './plugin.js';
|
package/lib/plugins/alias.js
CHANGED
|
@@ -20,6 +20,38 @@
|
|
|
20
20
|
// resolve through the "import" condition (Rolldown's default), bypassing the
|
|
21
21
|
// `cjs-compat.cjs` shim that unwraps named-export ESM modules to their
|
|
22
22
|
// constructor — breaking `util.inherits(Child, Stream)` patterns.
|
|
23
|
+
//
|
|
24
|
+
// ── Two invariants this plugin holds ────────────────────────────────────────
|
|
25
|
+
//
|
|
26
|
+
// 1. **Gjsify-generated modules are NOT aliased.** An import whose importer is
|
|
27
|
+
// a `\0gjsify-*` virtual module (`isGjsifyVirtualModuleId`) is left alone.
|
|
28
|
+
// We wrote that module's source; it names the RUNTIME module it needs and
|
|
29
|
+
// must get exactly that. The motivating failure: `gjsGiNodePlugin`'s
|
|
30
|
+
// `import { createRequire } from 'node:module'` was rewritten onto the
|
|
31
|
+
// `@gjsify/module` polyfill by the consumer harness's
|
|
32
|
+
// `--alias node:module=@gjsify/module`, so the bundle called the polyfill's
|
|
33
|
+
// `createRequire` at top level before its GLib proxy existed
|
|
34
|
+
// (`TypeError: … reading 'filename_from_uri'`) — and the polyfill's own
|
|
35
|
+
// Gio-backed loader can only reach GLib THROUGH the module it was being
|
|
36
|
+
// asked to load. The guard is importer-based rather than a per-specifier
|
|
37
|
+
// carve-out so it covers every current and future virtual module (entry
|
|
38
|
+
// wrapper, gi-node, napi-addon, gjs-imports-empty).
|
|
39
|
+
//
|
|
40
|
+
// 2. **An alias target is TERMINAL — exactly one hop, never a chain.** The
|
|
41
|
+
// `entries` map handed in is already MERGED across tiers (derived slot
|
|
42
|
+
// routing → curated `ALIASES_*` tables → per-target overrides → user
|
|
43
|
+
// `--alias`, later wins, see `app/*.ts`), so the tier a value came from is
|
|
44
|
+
// no longer recoverable here. Re-feeding a target through the table would
|
|
45
|
+
// therefore apply slot routing to USER aliases too, which is precisely what
|
|
46
|
+
// must not happen: `scripts/node-gi-consumer-harness.mjs` builds
|
|
47
|
+
// `--alias node:<x>=@gjsify/<x>` to put the POLYFILL under test behind a
|
|
48
|
+
// Node builtin, and 34 of those polyfills declare `runtimes.node: "native"`
|
|
49
|
+
// — a second hop would silently route them back to `@gjsify/<x>/globals`
|
|
50
|
+
// (i.e. Node's own builtin) and the suite would measure nothing. The
|
|
51
|
+
// "second pass" of the slot-routing model is a COMPOSITION concern, and it
|
|
52
|
+
// is resolved where the tiers are still distinguishable — see
|
|
53
|
+
// `withDerivedSlotRouting` in `@gjsify/resolve-npm`.
|
|
54
|
+
import { isGjsifyVirtualModuleId } from '../utils/virtual-module-id.js';
|
|
23
55
|
export function aliasPlugin(options) {
|
|
24
56
|
const entries = options.entries;
|
|
25
57
|
return {
|
|
@@ -27,6 +59,10 @@ export function aliasPlugin(options) {
|
|
|
27
59
|
resolveId: {
|
|
28
60
|
order: 'pre',
|
|
29
61
|
async handler(source, importer, extraOptions) {
|
|
62
|
+
// Invariant 1 — never rewrite an import that WE generated.
|
|
63
|
+
if (isGjsifyVirtualModuleId(importer)) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
30
66
|
if (!Object.prototype.hasOwnProperty.call(entries, source)) {
|
|
31
67
|
return null;
|
|
32
68
|
}
|
|
@@ -121,7 +121,32 @@ async function tryLoadNativeBundler() {
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
async function loadNpmBundler() {
|
|
124
|
-
|
|
124
|
+
// INDIRECT specifier, for the same reason `tryLoadNativeBundler` uses one:
|
|
125
|
+
// keep this acquisition out of the BUILD graph so it is resolved at
|
|
126
|
+
// runtime only. npm `lightningcss` is a napi-rs package, and a static
|
|
127
|
+
// `import('lightningcss')` here put its generated loader into the module
|
|
128
|
+
// graph of every `--app gjs` bundle — including the CLI's own committed
|
|
129
|
+
// `dist/cli.gjs.mjs`.
|
|
130
|
+
//
|
|
131
|
+
// That is what made the committed bundle ENVIRONMENT-DEPENDENT. With
|
|
132
|
+
// `@gjsify/napi` resolvable, `napiNodeAddonPlugin` rewrites the loader to a
|
|
133
|
+
// `loadAddon()` shim and inlines it; without it the plugin declines and the
|
|
134
|
+
// import stays external. Two different module graphs, so the minifier
|
|
135
|
+
// assigns different variable names, so the artifact stops reproducing from
|
|
136
|
+
// its own source (`verify-committed-bundles` red, first difference at byte
|
|
137
|
+
// 275: `r=` vs `a=` for `globalThis.imports.gi.GLib`). A toolchain artifact
|
|
138
|
+
// must not depend on whether an optional package happened to be installed
|
|
139
|
+
// when it was built.
|
|
140
|
+
//
|
|
141
|
+
// Nothing is lost under GJS: the documented GJS backend is
|
|
142
|
+
// `@gjsify/lightningcss-native` (above), and this npm fallback was never
|
|
143
|
+
// actually usable there — its `.node` cannot load under GJS without a
|
|
144
|
+
// build-time rewrite, and the only arches where that rewrite is possible
|
|
145
|
+
// (`@gjsify/napi`: linux-x64, darwin-arm64) are arches where the native
|
|
146
|
+
// bridge exists anyway. On Node this is unchanged: `lightningcss` is a real
|
|
147
|
+
// `dependency` of this package, so the runtime resolve finds it.
|
|
148
|
+
const specifier = 'lightningcss';
|
|
149
|
+
const { bundleAsync } = (await import(/* @vite-ignore */ specifier));
|
|
125
150
|
return async (filename, targets) => {
|
|
126
151
|
const result = await bundleAsync({
|
|
127
152
|
filename,
|
|
@@ -27,7 +27,11 @@
|
|
|
27
27
|
// Portability note (same as `gjs-imports-empty.ts`): the `filter` is a Rolldown
|
|
28
28
|
// fast-path; the internal guard in the handler is the load-bearing check so the
|
|
29
29
|
// plugin is correct even when the filter does not pre-filter (e.g. under Vite).
|
|
30
|
-
|
|
30
|
+
import { GJSIFY_VIRTUAL_PREFIX } from '../utils/virtual-module-id.js';
|
|
31
|
+
// Derived from the shared prefix so `isGjsifyVirtualModuleId` recognises these
|
|
32
|
+
// ids — that is what keeps the `node:module` import below bound to the RUNTIME's
|
|
33
|
+
// `createRequire` even when the build carries a user `--alias node:module=…`.
|
|
34
|
+
const GI_NODE_VIRTUAL_PREFIX = `${GJSIFY_VIRTUAL_PREFIX}gi-node:`;
|
|
31
35
|
/** Parse a `gi://Ns?version=X` specifier into its namespace + optional version. */
|
|
32
36
|
export function parseGiSpecifier(source) {
|
|
33
37
|
if (!source.startsWith('gi://'))
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
// dev server) the handler may receive ALL ids. The internal guard below makes
|
|
32
32
|
// the plugin correct regardless of whether the filter pre-filtered — it is
|
|
33
33
|
// the load-bearing check; the `filter` is a defense-in-depth optimization.
|
|
34
|
-
|
|
34
|
+
import { GJSIFY_VIRTUAL_PREFIX } from '../utils/virtual-module-id.js';
|
|
35
|
+
const GJSIMPORTS_VIRTUAL_ID = `${GJSIFY_VIRTUAL_PREFIX}empty-gjs-import`;
|
|
35
36
|
export function gjsImportsEmptyPlugin(options = {}) {
|
|
36
37
|
const emptyGirs = options.emptyGirs ?? true;
|
|
37
38
|
// When `@girs/*` is carved out, only `gi://*` reaches the empty redirect.
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import type { Plugin } from 'rolldown';
|
|
2
|
+
/**
|
|
3
|
+
* Fast-path filter (superset of what `claimSpecifier` claims). Handler re-checks.
|
|
4
|
+
*
|
|
5
|
+
* Exported so `napi-node-addon.spec.ts` can assert its Rust-compatibility (see
|
|
6
|
+
* {@link BARE_SPECIFIER_RE}) — a JS-only construct here disables the plugin
|
|
7
|
+
* entirely under `@gjsify/rolldown-native`, and no build-level test would see
|
|
8
|
+
* it, because every addon gate drives the Node CLI entry.
|
|
9
|
+
*/
|
|
10
|
+
export declare const ADDON_FILTER_RE: RegExp;
|
|
2
11
|
/** Options for {@link napiNodeAddonPlugin}. */
|
|
3
12
|
export interface NapiNodeAddonPluginOptions {
|
|
4
13
|
/**
|
|
@@ -54,6 +63,8 @@ export declare function classifySpecifier(source: string): {
|
|
|
54
63
|
export interface AddonPackageJson {
|
|
55
64
|
name?: string;
|
|
56
65
|
main?: string;
|
|
66
|
+
module?: string;
|
|
67
|
+
exports?: unknown;
|
|
57
68
|
browser?: string;
|
|
58
69
|
napi?: unknown;
|
|
59
70
|
optionalDependencies?: Record<string, string>;
|
|
@@ -65,11 +76,13 @@ export interface AddonPackageJson {
|
|
|
65
76
|
* (a) a top-level `napi` config OBJECT (`{ binaryName, targets, … }`) — the
|
|
66
77
|
* napi-rs CLI's own build block. An ordinary npm package never declares a
|
|
67
78
|
* `napi` object at the manifest root.
|
|
68
|
-
* (b) at least one `optionalDependencies` entry
|
|
69
|
-
* napi-rs prebuilt-binary convention
|
|
70
|
-
* `@node-rs/argon2
|
|
71
|
-
*
|
|
72
|
-
*
|
|
79
|
+
* (b) at least one `optionalDependencies` entry that is a platform SIBLING
|
|
80
|
+
* (see {@link isNapiRsSibling}) — the napi-rs prebuilt-binary convention,
|
|
81
|
+
* under either naming scheme: `<self>-<triple>` (`@node-rs/argon2` ships
|
|
82
|
+
* `@node-rs/argon2-linux-x64-gnu`) or `<napi.packageName>-<triple>`
|
|
83
|
+
* (`rolldown` ships `@rolldown/binding-linux-x64-gnu`). The key MUST
|
|
84
|
+
* carry a known prefix AND end in a platform triple, so a normal optional
|
|
85
|
+
* dep (`fsevents`) can't match.
|
|
73
86
|
*
|
|
74
87
|
* Neither fires on a normal package, so pairing this with the "resolved file IS
|
|
75
88
|
* the package's own native main entry" check (see {@link detectNapiRsEntry})
|
|
@@ -88,6 +101,13 @@ export declare function detectNapiRsEntry(entryFile: string): {
|
|
|
88
101
|
pkgRoot: string;
|
|
89
102
|
pkg: AddonPackageJson;
|
|
90
103
|
} | null;
|
|
104
|
+
/**
|
|
105
|
+
* Is `dep` a platform sibling of `pkg` — `<prefix>-<triple>` for one of the
|
|
106
|
+
* prefixes above? Both halves must hold: a bare prefix match would claim an
|
|
107
|
+
* ordinary dependency, and a bare triple match would claim an unrelated
|
|
108
|
+
* package's binaries. Exported for the unit tests.
|
|
109
|
+
*/
|
|
110
|
+
export declare function isNapiRsSibling(pkg: AddonPackageJson, dep: string): boolean;
|
|
91
111
|
/**
|
|
92
112
|
* Transparent `.node`-addon loader for `gjsify build --app gjs`.
|
|
93
113
|
*
|