@gjsify/rolldown-plugin-gjsify 0.23.0 → 0.24.1
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 +8 -4
- package/lib/app/gjs.d.ts +24 -0
- package/lib/app/gjs.js +62 -22
- package/lib/app/index.d.ts +1 -1
- package/lib/app/index.js +1 -1
- package/lib/app/nativescript.js +15 -4
- package/lib/index.d.ts +3 -1
- package/lib/index.js +2 -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/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';
|
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,6 +10,8 @@
|
|
|
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';
|
|
@@ -23,35 +25,38 @@ import { cssAsStringPlugin } from '../plugins/css-as-string.js';
|
|
|
23
25
|
import { shebangPlugin, resolveShebangLine, inputShebangStripPlugin } from '../plugins/shebang.js';
|
|
24
26
|
import { wrapInputWithSideEffects } from '../utils/entry-wrapper.js';
|
|
25
27
|
const _shimDir = dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
/**
|
|
29
|
+
* Resolve one of this package's bundled shims to an absolute path.
|
|
30
|
+
*
|
|
31
|
+
* `createRequire` is a NAMED IMPORT from `node:module`, never a bare
|
|
32
|
+
* `require(...)` — this source is ESM (see AGENTS.md "Our source is ESM —
|
|
33
|
+
* no bare `require`"). It is used here for its RESOLVER, which is
|
|
34
|
+
* `exports`-map aware, and only to answer "where does this subpath live";
|
|
35
|
+
* nothing is loaded through it.
|
|
36
|
+
*/
|
|
26
37
|
function resolveShim(shimName) {
|
|
27
38
|
// Preferred: relative to this module's directory. Works under the
|
|
28
39
|
// normal Node consumer flow where `_shimDir` = `<pkg>/lib/app/`.
|
|
29
40
|
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))
|
|
41
|
+
if (existsSync(relative))
|
|
39
42
|
return relative;
|
|
40
43
|
// Fallback: when the orchestrator is bundled into a single .mjs
|
|
41
44
|
// (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.
|
|
45
|
+
// own directory and the relative lookup misses. The published subpath
|
|
46
|
+
// export `./shims/<name>` resolves under both Node and GJS without
|
|
47
|
+
// further walking. Each shim MUST be a `./shims/<name>` subpath export
|
|
48
|
+
// in package.json for this fallback to resolve.
|
|
47
49
|
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}`);
|
|
50
|
+
return createRequire(import.meta.url).resolve(`@gjsify/rolldown-plugin-gjsify/shims/${shimName}`);
|
|
52
51
|
}
|
|
53
|
-
catch {
|
|
54
|
-
|
|
52
|
+
catch (cause) {
|
|
53
|
+
// Both lookups failed — the shim is genuinely absent. Returning the
|
|
54
|
+
// unverified `relative` here would hand a non-existent path to the
|
|
55
|
+
// alias map, and the failure would resurface much later as an
|
|
56
|
+
// unrelated "could not resolve" naming a path nobody wrote. Name the
|
|
57
|
+
// actual problem, at the site that knows what it was looking for.
|
|
58
|
+
throw new Error(`@gjsify/rolldown-plugin-gjsify: cannot locate the "${shimName}" shim. ` +
|
|
59
|
+
`Tried ${relative} and the "./shims/${shimName}" subpath export.`, { cause });
|
|
55
60
|
}
|
|
56
61
|
}
|
|
57
62
|
export const setupForGjs = async (input) => {
|
|
@@ -72,7 +77,12 @@ export const setupForGjs = async (input) => {
|
|
|
72
77
|
//
|
|
73
78
|
// `exactExternal` filters out register subpaths so a user external
|
|
74
79
|
// entry can never override the force-inline carve-out below.
|
|
75
|
-
const exactExternal = [
|
|
80
|
+
const exactExternal = [
|
|
81
|
+
'cairo',
|
|
82
|
+
'gettext',
|
|
83
|
+
'system',
|
|
84
|
+
...userExternal.filter((id) => !isRegisterSubpath(id) && !isGjsifyShim(id)),
|
|
85
|
+
];
|
|
76
86
|
const external = createGjsExternalsPredicate(userExternal);
|
|
77
87
|
const format = input.pluginOptions.format ?? 'esm';
|
|
78
88
|
const exclude = input.pluginOptions.exclude ?? [];
|
|
@@ -271,7 +281,7 @@ function flattenAliases(map) {
|
|
|
271
281
|
export function createGjsExternalsPredicate(userExternal = []) {
|
|
272
282
|
const exact = ['cairo', 'gettext', 'system', ...userExternal];
|
|
273
283
|
return (id) => {
|
|
274
|
-
if (isRegisterSubpath(id))
|
|
284
|
+
if (isRegisterSubpath(id) || isGjsifyShim(id))
|
|
275
285
|
return false;
|
|
276
286
|
if (id.startsWith('gi://'))
|
|
277
287
|
return true;
|
|
@@ -280,6 +290,36 @@ export function createGjsExternalsPredicate(userExternal = []) {
|
|
|
280
290
|
return false;
|
|
281
291
|
};
|
|
282
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Recognize the shims this plugin INJECTS into a `--app gjs` bundle
|
|
295
|
+
* (`shims/module-resolve`, `shims/console-gjs`, `shims/unicorn-magic`, …), in
|
|
296
|
+
* both bare-specifier and resolved-disk-path form.
|
|
297
|
+
*
|
|
298
|
+
* They must be force-inlined for exactly the reason `/register` subpaths are,
|
|
299
|
+
* and the failure is the same one: GJS has no `require` and its ESM loader has
|
|
300
|
+
* neither a node_modules walker nor `exports`-map support, so an EXTERNALIZED
|
|
301
|
+
* shim aborts the program at load:
|
|
302
|
+
*
|
|
303
|
+
* Error: Calling `require` for
|
|
304
|
+
* "@gjsify/rolldown-plugin-gjsify/shims/module-resolve" in an environment
|
|
305
|
+
* that doesn't expose the `require` function
|
|
306
|
+
*
|
|
307
|
+
* The shim is not optional — the bundler injected it because the bundle needs
|
|
308
|
+
* it — so externalizing it can only ever produce an unloadable artifact. The
|
|
309
|
+
* carve-out short-circuits BEFORE the user-external check so `bundler.external`
|
|
310
|
+
* cannot override it, and it matches by SHAPE rather than an explicit list, so
|
|
311
|
+
* a shim added later is covered without touching this predicate.
|
|
312
|
+
*
|
|
313
|
+
* Scope is deliberately narrow: only OUR shim directory. A consumer package
|
|
314
|
+
* that happens to have a `shims/` folder is unaffected.
|
|
315
|
+
*/
|
|
316
|
+
export function isGjsifyShim(id) {
|
|
317
|
+
// Bare/fully-qualified: `@gjsify/rolldown-plugin-gjsify/shims/<name>`.
|
|
318
|
+
if (/@gjsify\/rolldown-plugin-gjsify\/shims\/[^?]+$/.test(id))
|
|
319
|
+
return true;
|
|
320
|
+
// Resolved disk path: `…/rolldown-plugin-gjsify/{lib,src}/shims/<name>.js`.
|
|
321
|
+
return /[\\/]rolldown-plugin-gjsify[\\/](?:lib|src)[\\/]shims[\\/]/.test(id);
|
|
322
|
+
}
|
|
283
323
|
/**
|
|
284
324
|
* Recognize the `/register` and `/register/<feature>` subpath shapes that
|
|
285
325
|
* `--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).
|
package/lib/index.d.ts
CHANGED
|
@@ -11,10 +11,12 @@ 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 { napiNodeAddonPlugin, resolveAddonPath, nearestPackageRoot, classifySpecifier, directNodeShim, nodeGypBuildShim, bindingsShim, napiRsShim, isNapiRsPackageJson, detectNapiRsEntry, AddonNotBuiltError, } from './plugins/napi-node-addon.js';
|
|
19
|
+
export { napiNodeAddonPlugin, resolveAddonPath, nearestPackageRoot, classifySpecifier, directNodeShim, nodeGypBuildShim, bindingsShim, napiRsShim, ADDON_FILTER_RE, isNapiRsPackageJson, isNapiRsSibling, detectNapiRsEntry, AddonNotBuiltError, } from './plugins/napi-node-addon.js';
|
|
18
20
|
export type { NapiNodeAddonPluginOptions, AddonPackageJson } from './plugins/napi-node-addon.js';
|
|
19
21
|
export { platformResolvePlugin, detectNativescriptPlatform, nativescriptPlatformDefines, } from './plugins/platform-resolve.js';
|
|
20
22
|
export type { PlatformResolvePluginOptions, NativescriptPlatform } from './plugins/platform-resolve.js';
|
package/lib/index.js
CHANGED
|
@@ -8,9 +8,10 @@ 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 { napiNodeAddonPlugin, resolveAddonPath, nearestPackageRoot, classifySpecifier, directNodeShim, nodeGypBuildShim, bindingsShim, napiRsShim, isNapiRsPackageJson, detectNapiRsEntry, AddonNotBuiltError, } from './plugins/napi-node-addon.js';
|
|
14
|
+
export { napiNodeAddonPlugin, resolveAddonPath, nearestPackageRoot, classifySpecifier, directNodeShim, nodeGypBuildShim, bindingsShim, napiRsShim, ADDON_FILTER_RE, isNapiRsPackageJson, isNapiRsSibling, detectNapiRsEntry, AddonNotBuiltError, } from './plugins/napi-node-addon.js';
|
|
14
15
|
export { platformResolvePlugin, detectNativescriptPlatform, nativescriptPlatformDefines, } from './plugins/platform-resolve.js';
|
|
15
16
|
export * from './plugin.js';
|
|
16
17
|
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
|
*
|
|
@@ -38,9 +38,12 @@
|
|
|
38
38
|
// LOCATE the `.node` but cannot rescue that body. So when the entry of a napi-rs
|
|
39
39
|
// package resolves, we replace the WHOLE module with
|
|
40
40
|
// `module.exports = loadAddon('<abs platform .node>')` (the `napi-rs-entry`
|
|
41
|
-
// kind) — the platform `.node` is the current-triple sibling package
|
|
42
|
-
//
|
|
43
|
-
//
|
|
41
|
+
// kind) — the platform `.node` is the current-triple sibling package (only the
|
|
42
|
+
// host's optionalDependency is installed) or a local `pkg.<triple>.node`. The
|
|
43
|
+
// sibling is named `<self>-<triple>` OR `<napi.packageName>-<triple>`; both
|
|
44
|
+
// schemes ship in the wild and `isNapiRsSibling` knows both, which is what
|
|
45
|
+
// makes `rolldown`/`oxfmt`/`oxlint` (all `@<scope>/binding-<triple>`) reachable
|
|
46
|
+
// and not just `lightningcss`/`@node-rs/*`. Detection is CONSERVATIVE (package.json signal +
|
|
44
47
|
// the file must be the package's own native `main` entry + a real host `.node`
|
|
45
48
|
// must resolve) so it never rewrites an unrelated package's entry; when no
|
|
46
49
|
// current-platform `.node` resolves we fall through to normal resolution rather
|
|
@@ -66,7 +69,8 @@
|
|
|
66
69
|
// plugin is correct even when the filter does not pre-filter.
|
|
67
70
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
68
71
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
69
|
-
|
|
72
|
+
import { GJSIFY_VIRTUAL_PREFIX } from '../utils/virtual-module-id.js';
|
|
73
|
+
const NAPI_ADDON_VIRTUAL_PREFIX = `${GJSIFY_VIRTUAL_PREFIX}napi-addon:`;
|
|
70
74
|
/** Bare specifier the shims import — resolved + bundled from the consumer graph. */
|
|
71
75
|
const NAPI_BARE_SPECIFIER = '@gjsify/napi';
|
|
72
76
|
/**
|
|
@@ -78,8 +82,41 @@ const NAPI_BARE_SPECIFIER = '@gjsify/napi';
|
|
|
78
82
|
* `this.resolve` probes on ordinary deps.
|
|
79
83
|
*/
|
|
80
84
|
const NAPI_RS_TRIPLE_RE = /-(?:linux|darwin|win32|freebsd|openbsd|sunos|android|aix)-(?:x64|arm64|arm|ia32|ppc64|s390x|riscv64|loong64)(?:-(?:gnu|musl|msvc|eabi|eabihf|androideabi|gnueabihf))?$/;
|
|
81
|
-
/**
|
|
82
|
-
|
|
85
|
+
/**
|
|
86
|
+
* A BARE package specifier (`lightningcss`, `@rolldown/binding-…`) — no leading
|
|
87
|
+
* `.`/`/`/`\`, no protocol (`node:fs`, `gi://Gtk`, `data:…`, `C:\…`), not a
|
|
88
|
+
* `\0` virtual id.
|
|
89
|
+
*
|
|
90
|
+
* Needed because a napi-rs generated loader acquires its binary through a
|
|
91
|
+
* specifier the bundler CANNOT see: `require(`lightningcss-${parts.join('-')}`)`
|
|
92
|
+
* / `require(`../lightningcss.${parts.join('-')}.node`)` are template literals
|
|
93
|
+
* computed at runtime. So neither the sibling nor the direct-`.node`
|
|
94
|
+
* interception can fire, and the only lever is replacing the loader ENTRY —
|
|
95
|
+
* which the caller reaches by its BARE name. Matching only path-shaped ids (as
|
|
96
|
+
* before) meant `import { transform } from 'lightningcss'` was never even
|
|
97
|
+
* offered to the handler, and the addon gate had to hand-alias every bare
|
|
98
|
+
* specifier to its native entry to get coverage.
|
|
99
|
+
*
|
|
100
|
+
* PORTABILITY, and it is load-bearing: this source feeds `ADDON_FILTER_RE`,
|
|
101
|
+
* which `@gjsify/rolldown-native` hands to the Rust core as an `idFilter`
|
|
102
|
+
* STRING. Rust's `regex` crate has no lookaround and no `\0` escape, and it
|
|
103
|
+
* rejects the WHOLE combined filter rather than the offending branch — which
|
|
104
|
+
* silently disables every interception under the GJS engine while npm
|
|
105
|
+
* `rolldown` on Node keeps working. So: no `(?!…)`, and NUL is spelled `\x00`.
|
|
106
|
+
* The asymmetry is invisible to the addon gate, which drives the Node CLI
|
|
107
|
+
* entry, so only a real `--app gjs` build under `gjs` catches a regression here.
|
|
108
|
+
*/
|
|
109
|
+
// oxlint-disable-next-line no-control-regex -- the NUL is the point: it marks a bundler VIRTUAL id (`\0gjsify-…`), which is never a package specifier and must not be probed.
|
|
110
|
+
const BARE_SPECIFIER_RE = /^[^.\\/:\s\x00][^:\s\x00]*$/;
|
|
111
|
+
/**
|
|
112
|
+
* Fast-path filter (superset of what `claimSpecifier` claims). Handler re-checks.
|
|
113
|
+
*
|
|
114
|
+
* Exported so `napi-node-addon.spec.ts` can assert its Rust-compatibility (see
|
|
115
|
+
* {@link BARE_SPECIFIER_RE}) — a JS-only construct here disables the plugin
|
|
116
|
+
* entirely under `@gjsify/rolldown-native`, and no build-level test would see
|
|
117
|
+
* it, because every addon gate drives the Node CLI entry.
|
|
118
|
+
*/
|
|
119
|
+
export const ADDON_FILTER_RE = new RegExp([
|
|
83
120
|
/\.node$/.source, // direct .node
|
|
84
121
|
/^node-gyp-build(?:\/index\.js)?$/.source, // node-gyp-build helper
|
|
85
122
|
/^bindings(?:\/bindings\.js)?$/.source, // bindings helper
|
|
@@ -89,6 +126,9 @@ const ADDON_FILTER_RE = new RegExp([
|
|
|
89
126
|
// `index.*`, not every `.js`) — the handler's `detectNapiRsEntry` gate
|
|
90
127
|
// (package.json signal + native-main match) is the load-bearing check.
|
|
91
128
|
/[/\\]index\.[cm]?js$/.source,
|
|
129
|
+
// A bare specifier, so the handler can resolve it and test the RESOLVED
|
|
130
|
+
// file. Broad by design; the handler's fast rejects keep it cheap.
|
|
131
|
+
BARE_SPECIFIER_RE.source,
|
|
92
132
|
].join('|'));
|
|
93
133
|
function isMusl(platform) {
|
|
94
134
|
// Match node-gyp-build's isAlpine() probe.
|
|
@@ -351,11 +391,13 @@ export function classifySpecifier(source) {
|
|
|
351
391
|
* (a) a top-level `napi` config OBJECT (`{ binaryName, targets, … }`) — the
|
|
352
392
|
* napi-rs CLI's own build block. An ordinary npm package never declares a
|
|
353
393
|
* `napi` object at the manifest root.
|
|
354
|
-
* (b) at least one `optionalDependencies` entry
|
|
355
|
-
* napi-rs prebuilt-binary convention
|
|
356
|
-
* `@node-rs/argon2
|
|
357
|
-
*
|
|
358
|
-
*
|
|
394
|
+
* (b) at least one `optionalDependencies` entry that is a platform SIBLING
|
|
395
|
+
* (see {@link isNapiRsSibling}) — the napi-rs prebuilt-binary convention,
|
|
396
|
+
* under either naming scheme: `<self>-<triple>` (`@node-rs/argon2` ships
|
|
397
|
+
* `@node-rs/argon2-linux-x64-gnu`) or `<napi.packageName>-<triple>`
|
|
398
|
+
* (`rolldown` ships `@rolldown/binding-linux-x64-gnu`). The key MUST
|
|
399
|
+
* carry a known prefix AND end in a platform triple, so a normal optional
|
|
400
|
+
* dep (`fsevents`) can't match.
|
|
359
401
|
*
|
|
360
402
|
* Neither fires on a normal package, so pairing this with the "resolved file IS
|
|
361
403
|
* the package's own native main entry" check (see {@link detectNapiRsEntry})
|
|
@@ -364,11 +406,10 @@ export function classifySpecifier(source) {
|
|
|
364
406
|
export function isNapiRsPackageJson(pkg) {
|
|
365
407
|
if (pkg && typeof pkg.napi === 'object' && pkg.napi !== null)
|
|
366
408
|
return true;
|
|
367
|
-
const self = pkg?.name;
|
|
368
409
|
const opt = pkg?.optionalDependencies;
|
|
369
|
-
if (
|
|
410
|
+
if (opt) {
|
|
370
411
|
for (const dep of Object.keys(opt)) {
|
|
371
|
-
if (
|
|
412
|
+
if (isNapiRsSibling(pkg, dep))
|
|
372
413
|
return true;
|
|
373
414
|
}
|
|
374
415
|
}
|
|
@@ -399,13 +440,79 @@ export function detectNapiRsEntry(entryFile) {
|
|
|
399
440
|
const pkg = readPackageJsonSafe(pkgRoot);
|
|
400
441
|
if (pkg === null || !isNapiRsPackageJson(pkg))
|
|
401
442
|
return null;
|
|
402
|
-
// The resolved file must be the package's OWN native
|
|
403
|
-
// `browser`/wasm fallback or any deep module inside the package.
|
|
404
|
-
const
|
|
405
|
-
|
|
443
|
+
// The resolved file must be one of the package's OWN native entry points —
|
|
444
|
+
// reject a `browser`/wasm fallback or any deep module inside the package.
|
|
445
|
+
const target = stripJsExt(resolve(entryFile));
|
|
446
|
+
const isNativeEntry = nativeEntrySpecs(pkg).some((spec) => stripJsExt(resolve(pkgRoot, spec)) === target);
|
|
447
|
+
if (!isNativeEntry)
|
|
406
448
|
return null;
|
|
407
449
|
return { pkgRoot, pkg };
|
|
408
450
|
}
|
|
451
|
+
/** Drop a trailing `.js` / `.mjs` / `.cjs` — see {@link nativeEntrySpecs}. */
|
|
452
|
+
function stripJsExt(p) {
|
|
453
|
+
return p.replace(/\.[cm]?js$/, '');
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Every path a napi-rs manifest names as its NATIVE entry — `main`, `module`,
|
|
457
|
+
* and the `exports["."]` target(s) — with the `browser` condition deliberately
|
|
458
|
+
* excluded (that is the wasm / pure-JS fallback, which must keep resolving
|
|
459
|
+
* normally: it needs no addon and works on GJS as-is).
|
|
460
|
+
*
|
|
461
|
+
* Both halves of this matter, and each was a real miss:
|
|
462
|
+
*
|
|
463
|
+
* - `main` ALONE is not enough. A dual package declares the CJS twin in
|
|
464
|
+
* `main` while a bundler resolves the ESM one — `lightningcss` says
|
|
465
|
+
* `main: "node/index.js"` and ships `node/index.mjs` beside it, which is
|
|
466
|
+
* what `--app gjs` actually loads. Comparing only against `main` rejected
|
|
467
|
+
* the very file being bundled, so the rewrite silently never happened and
|
|
468
|
+
* the generated loader's runtime `require(\`lightningcss-${…}\`)` shipped
|
|
469
|
+
* into the bundle.
|
|
470
|
+
* - Matching is therefore extension-INSENSITIVE (`stripJsExt`): the `.js` /
|
|
471
|
+
* `.mjs` / `.cjs` twins of a declared entry are the same generated loader.
|
|
472
|
+
* It stays narrow because the path itself must still be one the manifest
|
|
473
|
+
* names — a deep module inside the package never matches.
|
|
474
|
+
*
|
|
475
|
+
* Defaults to `index.js` (node's own default `main`) when the manifest names
|
|
476
|
+
* nothing at all.
|
|
477
|
+
*/
|
|
478
|
+
function nativeEntrySpecs(pkg) {
|
|
479
|
+
const specs = [];
|
|
480
|
+
const add = (v) => {
|
|
481
|
+
if (typeof v === 'string' && v && !specs.includes(v))
|
|
482
|
+
specs.push(v);
|
|
483
|
+
};
|
|
484
|
+
add(pkg.main);
|
|
485
|
+
add(pkg.module);
|
|
486
|
+
// `exports["."]` is a string, or a condition object whose values are
|
|
487
|
+
// strings / nested condition objects. Walk it, skipping `browser`.
|
|
488
|
+
const walk = (node, depth) => {
|
|
489
|
+
if (typeof node === 'string')
|
|
490
|
+
return add(node);
|
|
491
|
+
if (depth > 4 || node === null || typeof node !== 'object' || Array.isArray(node))
|
|
492
|
+
return;
|
|
493
|
+
for (const [condition, value] of Object.entries(node)) {
|
|
494
|
+
if (condition === 'browser' || condition === 'types')
|
|
495
|
+
continue;
|
|
496
|
+
walk(value, depth + 1);
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
const exportsField = pkg.exports;
|
|
500
|
+
if (typeof exportsField === 'string') {
|
|
501
|
+
add(exportsField);
|
|
502
|
+
}
|
|
503
|
+
else if (exportsField !== null && typeof exportsField === 'object' && !Array.isArray(exportsField)) {
|
|
504
|
+
const map = exportsField;
|
|
505
|
+
// Two legal shapes: a SUBPATH map (keys start with `.`) — then only `.`
|
|
506
|
+
// is the package entry — or a bare CONDITION object, which IS the entry.
|
|
507
|
+
// Distinguishing them matters: walking a subpath map wholesale would
|
|
508
|
+
// make `./some-subpath` a candidate entry.
|
|
509
|
+
const isSubpathMap = Object.keys(map).some((k) => k.startsWith('.'));
|
|
510
|
+
walk(isSubpathMap ? map['.'] : map, 0);
|
|
511
|
+
}
|
|
512
|
+
if (specs.length === 0)
|
|
513
|
+
specs.push('index.js');
|
|
514
|
+
return specs;
|
|
515
|
+
}
|
|
409
516
|
/** napi-rs binaryName (from `napi.binaryName`/`napi.name`, else the unscoped pkg name). */
|
|
410
517
|
function napiBinaryName(pkg) {
|
|
411
518
|
const napi = pkg.napi;
|
|
@@ -419,13 +526,66 @@ function napiBinaryName(pkg) {
|
|
|
419
526
|
return pkg.name.replace(/^@[^/]+\//, '');
|
|
420
527
|
return null;
|
|
421
528
|
}
|
|
529
|
+
/**
|
|
530
|
+
* The name PREFIXES a napi-rs package's platform siblings can carry, most
|
|
531
|
+
* specific first. A sibling is always `<prefix>-<triple>`.
|
|
532
|
+
*
|
|
533
|
+
* Two conventions are in the wild, and only knowing both covers the toolchain
|
|
534
|
+
* this project builds on:
|
|
535
|
+
*
|
|
536
|
+
* - `<self>-<triple>` — the sibling is named after the package itself
|
|
537
|
+
* (`lightningcss` → `lightningcss-linux-x64-gnu`; `@node-rs/argon2` →
|
|
538
|
+
* `@node-rs/argon2-linux-x64-gnu`). This was the only shape handled.
|
|
539
|
+
* - `<napi.packageName>-<triple>` — napi-rs lets a package publish its
|
|
540
|
+
* binaries under a SEPARATE scope, declared in the manifest's own napi
|
|
541
|
+
* block. `rolldown` ships `@rolldown/binding-linux-x64-gnu`, `oxfmt`
|
|
542
|
+
* ships `@oxfmt/binding-linux-x64-gnu`, `oxlint` ships
|
|
543
|
+
* `@oxlint/binding-linux-x64-gnu` — none of which starts with the
|
|
544
|
+
* package's own name, so the `<self>-` test missed every one of them.
|
|
545
|
+
*
|
|
546
|
+
* That gap is why the three packages at the centre of this build chain
|
|
547
|
+
* (bundler, formatter, linter) could not load transparently under GJS while
|
|
548
|
+
* `lightningcss` could: `isNapiRsPackageJson` recognises all four (they all
|
|
549
|
+
* carry a `napi` config object), so the ENTRY is detected — but with no
|
|
550
|
+
* sibling resolvable, `resolveNapiRsEntryAddon` returns null and the rewrite
|
|
551
|
+
* is skipped, leaving the generated loader's CJS body to ship into the bundle
|
|
552
|
+
* and throw. Conservative by construction either way: a prefix only matters
|
|
553
|
+
* when it also resolves to a real `.node`.
|
|
554
|
+
*
|
|
555
|
+
* `napi.packageName` is read defensively — an arbitrary manifest may put
|
|
556
|
+
* anything there, and a non-string must not poison the prefix list.
|
|
557
|
+
*/
|
|
558
|
+
function napiSiblingPrefixes(pkg) {
|
|
559
|
+
const prefixes = [];
|
|
560
|
+
const napi = pkg.napi;
|
|
561
|
+
if (napi && typeof napi === 'object') {
|
|
562
|
+
const { packageName } = napi;
|
|
563
|
+
if (typeof packageName === 'string' && packageName)
|
|
564
|
+
prefixes.push(packageName);
|
|
565
|
+
}
|
|
566
|
+
if (typeof pkg.name === 'string' && pkg.name && !prefixes.includes(pkg.name))
|
|
567
|
+
prefixes.push(pkg.name);
|
|
568
|
+
return prefixes;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Is `dep` a platform sibling of `pkg` — `<prefix>-<triple>` for one of the
|
|
572
|
+
* prefixes above? Both halves must hold: a bare prefix match would claim an
|
|
573
|
+
* ordinary dependency, and a bare triple match would claim an unrelated
|
|
574
|
+
* package's binaries. Exported for the unit tests.
|
|
575
|
+
*/
|
|
576
|
+
export function isNapiRsSibling(pkg, dep) {
|
|
577
|
+
if (!NAPI_RS_TRIPLE_RE.test(dep))
|
|
578
|
+
return false;
|
|
579
|
+
return napiSiblingPrefixes(pkg).some((prefix) => dep.startsWith(`${prefix}-`));
|
|
580
|
+
}
|
|
422
581
|
/**
|
|
423
582
|
* The napi-rs short platform triple for the CURRENT host (`linux-x64-gnu`,
|
|
424
583
|
* `darwin-arm64`, `win32-x64-msvc`, …) — the tail napi-rs stamps into a sibling
|
|
425
584
|
* package name (`@node-rs/argon2-linux-x64-gnu`) and a local binary
|
|
426
|
-
* (`argon2.linux-x64-gnu.node`).
|
|
427
|
-
* fallback
|
|
428
|
-
*
|
|
585
|
+
* (`argon2.linux-x64-gnu.node`). Selects BOTH the sibling package and the
|
|
586
|
+
* local-file fallback — see `resolveNapiRsEntryAddon` for why the sibling path
|
|
587
|
+
* cannot just take whatever resolves. Returns null for a host napi-rs doesn't
|
|
588
|
+
* name, and the caller then widens rather than guessing.
|
|
429
589
|
*/
|
|
430
590
|
function hostNapiRsTriple() {
|
|
431
591
|
const platform = process.platform;
|
|
@@ -477,24 +637,29 @@ function rawAddonPath(id) {
|
|
|
477
637
|
* DOES NOT rewrite, so we never shim over a missing file.
|
|
478
638
|
*/
|
|
479
639
|
async function resolveNapiRsEntryAddon(ctx, pkgRoot, pkg, importer) {
|
|
480
|
-
const
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
640
|
+
const siblings = Object.keys(pkg.optionalDependencies ?? {}).filter((dep) => isNapiRsSibling(pkg, dep));
|
|
641
|
+
const triple = hostNapiRsTriple();
|
|
642
|
+
// HOST TRIPLE FIRST, and alone whenever we can name it. The older code took
|
|
643
|
+
// the first sibling that resolved, on the premise that "npm installs only
|
|
644
|
+
// the host's optionalDependency, so the one that resolves is the host's".
|
|
645
|
+
// That premise does not hold here: `gjsify install` materialises EVERY
|
|
646
|
+
// platform package, so `lightningcss`'s `darwin-x64` sibling resolves on a
|
|
647
|
+
// Linux box and won — baking a Mach-O `.dylib`-bearing `.node` into a linux
|
|
648
|
+
// GJS bundle, which `loadAddon` can only fail on at runtime. Selecting by
|
|
649
|
+
// the host triple is also what node-gyp-build and napi-rs' own generated
|
|
650
|
+
// loaders do, so this matches the binary Node would have loaded.
|
|
651
|
+
const ordered = triple === null ? siblings : siblings.filter((dep) => dep.endsWith(`-${triple}`));
|
|
652
|
+
for (const dep of ordered) {
|
|
653
|
+
const resolved = await ctx.resolve(dep, importer, { skipSelf: true });
|
|
654
|
+
if (!resolved)
|
|
655
|
+
continue;
|
|
656
|
+
const abs = rawAddonPath(resolved.id);
|
|
657
|
+
if (abs.endsWith('.node') && existsSync(abs))
|
|
658
|
+
return abs;
|
|
493
659
|
}
|
|
494
660
|
// Local in-package binary (`<binaryName>.<host-triple>.node`) — deterministic
|
|
495
661
|
// host-triple match so a wrong-platform local file is never picked.
|
|
496
662
|
const binaryName = napiBinaryName(pkg);
|
|
497
|
-
const triple = hostNapiRsTriple();
|
|
498
663
|
if (binaryName && triple) {
|
|
499
664
|
const local = join(pkgRoot, `${binaryName}.${triple}.node`);
|
|
500
665
|
if (existsSync(local))
|
|
@@ -555,28 +720,107 @@ export function napiNodeAddonPlugin(options = {}) {
|
|
|
555
720
|
napiEntryCache.set(entryFile, info);
|
|
556
721
|
return info;
|
|
557
722
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
723
|
+
// Bare-specifier → entry-file resolution, memoized per (specifier, importer
|
|
724
|
+
// DIRECTORY) since that pair is what node resolution actually depends on.
|
|
725
|
+
// The widened filter offers every bare import here and almost none of them
|
|
726
|
+
// is a native addon, so this cache is what keeps the extra `ctx.resolve` to
|
|
727
|
+
// one per unique pair rather than one per import site.
|
|
728
|
+
const bareEntryCache = new Map();
|
|
729
|
+
/**
|
|
730
|
+
* The FILE a specifier denotes, for napi-rs entry detection:
|
|
731
|
+
* - absolute path → itself (an already-resolved or hand-aliased entry).
|
|
732
|
+
* - bare specifier → resolved through the full chain (so aliases and
|
|
733
|
+
* export conditions apply — under `--app gjs` that is what picks a
|
|
734
|
+
* package's `browser`/wasm fallback over its native entry, and
|
|
735
|
+
* `detectNapiRsEntry` then correctly declines to rewrite it).
|
|
736
|
+
* - anything else (relative paths) → null, unchanged from before.
|
|
737
|
+
*/
|
|
738
|
+
async function entryFileFor(ctx, source, importer) {
|
|
739
|
+
if (isAbsolute(source))
|
|
740
|
+
return source;
|
|
741
|
+
if (!BARE_SPECIFIER_RE.test(source))
|
|
742
|
+
return null;
|
|
743
|
+
// Never recurse through our own shims' import.
|
|
744
|
+
if (source === NAPI_BARE_SPECIFIER || source.startsWith(`${NAPI_BARE_SPECIFIER}/`))
|
|
745
|
+
return null;
|
|
746
|
+
const key = `${source}\0${importer === undefined ? '' : dirname(importer)}`;
|
|
747
|
+
const cached = bareEntryCache.get(key);
|
|
748
|
+
if (cached !== undefined)
|
|
749
|
+
return cached;
|
|
750
|
+
let file = null;
|
|
562
751
|
try {
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
752
|
+
const resolved = await ctx.resolve(source, importer, { skipSelf: true });
|
|
753
|
+
// An external / virtual / unresolvable id is not a file on disk.
|
|
754
|
+
if (resolved && isAbsolute(resolved.id) && existsSync(resolved.id))
|
|
755
|
+
file = resolved.id;
|
|
568
756
|
}
|
|
569
757
|
catch {
|
|
570
|
-
/*
|
|
758
|
+
/* resolution failure is not our error to raise — fall through */
|
|
571
759
|
}
|
|
760
|
+
bareEntryCache.set(key, file);
|
|
761
|
+
return file;
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Is `@gjsify/napi` resolvable from the consumer graph? This is the GATE on
|
|
765
|
+
* every rewrite, not a diagnostic — installing it (`gjsify install
|
|
766
|
+
* @gjsify/napi`) is what a project uses to opt into napi routing.
|
|
767
|
+
*
|
|
768
|
+
* It used to only WARN, and the warning said the quiet part out loud: "the
|
|
769
|
+
* bundle will fail at load". Emitting a knowingly-unloadable artifact is
|
|
770
|
+
* never better than declining to rewrite — the untouched module at least
|
|
771
|
+
* gets the default resolution, and a package with a JS/wasm fallback then
|
|
772
|
+
* works. Two concrete failures made this a gate rather than a nicety:
|
|
773
|
+
*
|
|
774
|
+
* - The CLI's own `--app gjs` bundle reaches npm `lightningcss` through
|
|
775
|
+
* `css-as-string`'s Node fallback branch (under GJS it deliberately
|
|
776
|
+
* prefers `@gjsify/lightningcss-native`). Rewriting that branch put a
|
|
777
|
+
* `require('@gjsify/napi')` into a bundle whose package does not depend
|
|
778
|
+
* on `@gjsify/napi` at all.
|
|
779
|
+
* - Worse, it made the COMMITTED bundle environment-dependent: with the
|
|
780
|
+
* package present the shim inlines, without it the import goes external,
|
|
781
|
+
* and the two module graphs minify to different variable names. That is
|
|
782
|
+
* a `verify-committed-bundles` failure — the artifact stops reproducing
|
|
783
|
+
* from its own source.
|
|
784
|
+
*
|
|
785
|
+
* Probed once per build (interception is rare) and cached, so the extra
|
|
786
|
+
* resolve costs nothing on the common path.
|
|
787
|
+
*/
|
|
788
|
+
let napiAvailable = null;
|
|
789
|
+
async function ensureNapiAvailable(ctx, importer) {
|
|
790
|
+
if (napiAvailable !== null)
|
|
791
|
+
return napiAvailable;
|
|
792
|
+
let found = false;
|
|
793
|
+
try {
|
|
794
|
+
found = Boolean(await ctx.resolve(NAPI_BARE_SPECIFIER, importer, { skipSelf: true }));
|
|
795
|
+
}
|
|
796
|
+
catch {
|
|
797
|
+
/* best-effort probe — treat a resolver throw as "not available" */
|
|
798
|
+
}
|
|
799
|
+
napiAvailable = found;
|
|
800
|
+
if (!found && warnOnMissingNapi && !missingNapiChecked) {
|
|
801
|
+
missingNapiChecked = true;
|
|
802
|
+
warnSafe(ctx, `[gjsify-napi-addon] a native .node addon was found but '${NAPI_BARE_SPECIFIER}' is not ` +
|
|
803
|
+
`resolvable — leaving it to normal resolution. Install it to route the addon through ` +
|
|
804
|
+
`N-API on GJS: gjsify install ${NAPI_BARE_SPECIFIER}`);
|
|
805
|
+
}
|
|
806
|
+
return found;
|
|
572
807
|
}
|
|
573
808
|
return {
|
|
574
809
|
name: 'gjsify-napi-node-addon',
|
|
575
810
|
resolveId: {
|
|
576
811
|
order: 'pre',
|
|
577
812
|
filter: { id: ADDON_FILTER_RE },
|
|
578
|
-
async handler(source,
|
|
813
|
+
async handler(source, rawImporter) {
|
|
579
814
|
const ctx = this;
|
|
815
|
+
// The two engines disagree on "no importer": npm `rolldown`
|
|
816
|
+
// passes `undefined`, `@gjsify/rolldown-native` passes `null`
|
|
817
|
+
// (its hook payload round-trips through JSON, which has no
|
|
818
|
+
// `undefined`). Every guard below is written `=== undefined`,
|
|
819
|
+
// and `null` passes all of them — `dirname(null)` then threw
|
|
820
|
+
// `The "path" argument must be of type string. Received type
|
|
821
|
+
// object` and took the whole GJS build down as an
|
|
822
|
+
// UNHANDLEABLE_ERROR. Normalise once, at the boundary.
|
|
823
|
+
const importer = typeof rawImporter === 'string' ? rawImporter : undefined;
|
|
580
824
|
const cls = classifySpecifier(source);
|
|
581
825
|
if (cls !== null) {
|
|
582
826
|
// Direct `.node` — resolve the file path itself.
|
|
@@ -584,7 +828,8 @@ export function napiNodeAddonPlugin(options = {}) {
|
|
|
584
828
|
const abs = await resolveNodeFile(ctx, source, importer);
|
|
585
829
|
if (abs === null)
|
|
586
830
|
return null; // unresolvable — let the default chain error
|
|
587
|
-
await
|
|
831
|
+
if (!(await ensureNapiAvailable(ctx, importer)))
|
|
832
|
+
return null;
|
|
588
833
|
return { id: encodeVirtual('direct', abs) };
|
|
589
834
|
}
|
|
590
835
|
// napi-rs platform sibling — confirm it resolves to a `.node`.
|
|
@@ -592,7 +837,8 @@ export function napiNodeAddonPlugin(options = {}) {
|
|
|
592
837
|
const resolved = await ctx.resolve(source, importer, { skipSelf: true });
|
|
593
838
|
if (!resolved || !resolved.id.endsWith('.node'))
|
|
594
839
|
return null; // not a native sibling
|
|
595
|
-
await
|
|
840
|
+
if (!(await ensureNapiAvailable(ctx, importer)))
|
|
841
|
+
return null;
|
|
596
842
|
return { id: encodeVirtual('napi-rs', resolved.id) };
|
|
597
843
|
}
|
|
598
844
|
// node-gyp-build / bindings — probe the importer's package root.
|
|
@@ -601,8 +847,9 @@ export function napiNodeAddonPlugin(options = {}) {
|
|
|
601
847
|
const pkgRoot = nearestPackageRoot(importer);
|
|
602
848
|
if (pkgRoot === null)
|
|
603
849
|
return null;
|
|
850
|
+
if (!(await ensureNapiAvailable(ctx, importer)))
|
|
851
|
+
return null;
|
|
604
852
|
const addonPath = resolveAddonPath(pkgRoot, { warn: (m) => warnSafe(ctx, m) }); // throws → build error
|
|
605
|
-
await warnIfNapiMissing(ctx, importer);
|
|
606
853
|
return { id: encodeVirtual(cls.kind, addonPath) };
|
|
607
854
|
}
|
|
608
855
|
// napi-rs GENERATED-LOADER ENTRY (`index.*` filter). Replace the
|
|
@@ -610,11 +857,21 @@ export function napiNodeAddonPlugin(options = {}) {
|
|
|
610
857
|
// of a napi-rs package (package.json signal) AND a current-platform
|
|
611
858
|
// `.node` resolves. Otherwise fall through (never a shim over a
|
|
612
859
|
// missing file, never an unrelated package's entry rewritten).
|
|
613
|
-
|
|
860
|
+
// The specifier may be the entry PATH (an internal import, or a
|
|
861
|
+
// caller that already aliased it) or the package's BARE name —
|
|
862
|
+
// the ordinary `import { transform } from 'lightningcss'`. For
|
|
863
|
+
// the bare form the entry file is only known after resolution,
|
|
864
|
+
// so resolve first and test the resolved id. `entryFileFor`
|
|
865
|
+
// memoizes both the resolve and the rejection.
|
|
866
|
+
const entryFile = await entryFileFor(ctx, source, importer);
|
|
867
|
+
if (entryFile === null)
|
|
868
|
+
return null;
|
|
869
|
+
const entry = detectNapiRsEntryCached(entryFile);
|
|
614
870
|
if (entry !== null) {
|
|
615
|
-
const addonPath = await resolveNapiRsEntryAddon(ctx, entry.pkgRoot, entry.pkg,
|
|
871
|
+
const addonPath = await resolveNapiRsEntryAddon(ctx, entry.pkgRoot, entry.pkg, entryFile);
|
|
616
872
|
if (addonPath !== null) {
|
|
617
|
-
await
|
|
873
|
+
if (!(await ensureNapiAvailable(ctx, importer)))
|
|
874
|
+
return null;
|
|
618
875
|
return { id: encodeVirtual('napi-rs-entry', addonPath) };
|
|
619
876
|
}
|
|
620
877
|
}
|
|
@@ -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.1",
|
|
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.1",
|
|
59
|
+
"@gjsify/resolve-npm": "^0.24.1",
|
|
60
|
+
"@gjsify/rolldown-plugin-deepkit": "^0.24.1",
|
|
61
|
+
"@gjsify/rolldown-plugin-pnp": "^0.24.1",
|
|
62
|
+
"@gjsify/vite-plugin-blueprint": "^0.24.1",
|
|
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.1",
|
|
72
72
|
"rolldown": "^1.1.4"
|
|
73
73
|
},
|
|
74
74
|
"peerDependenciesMeta": {
|