@gjsify/rolldown-plugin-gjsify 0.42.0 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/app/browser.d.ts +2 -0
- package/lib/app/browser.js +4 -2
- package/lib/app/gjs.d.ts +2 -0
- package/lib/app/gjs.js +32 -2
- package/lib/app/nativescript.d.ts +2 -0
- package/lib/app/nativescript.js +6 -4
- package/lib/app/node.d.ts +7 -2
- package/lib/app/node.js +122 -4
- package/lib/index.d.ts +9 -1
- package/lib/index.js +5 -1
- package/lib/library/lib.d.ts +2 -0
- package/lib/library/lib.js +12 -1
- package/lib/plugin.d.ts +15 -0
- package/lib/plugins/intl-segmenter-banner.d.ts +1 -0
- package/lib/plugins/intl-segmenter-banner.js +42 -0
- package/lib/plugins/platform-resolve.d.ts +133 -8
- package/lib/plugins/platform-resolve.js +214 -30
- package/lib/plugins/process-stub.js +4 -0
- package/lib/plugins/react-native-alias.d.ts +63 -0
- package/lib/plugins/react-native-alias.js +128 -0
- package/lib/plugins/react-native-gate.d.ts +108 -0
- package/lib/plugins/react-native-gate.js +235 -0
- package/lib/plugins/rn-route-manifest.d.ts +67 -0
- package/lib/plugins/rn-route-manifest.js +166 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +1 -0
- package/lib/types/plugin-options.d.ts +20 -0
- package/lib/types/source-dialect.d.ts +42 -0
- package/lib/types/source-dialect.js +21 -0
- package/lib/utils/detect-free-globals.js +29 -7
- package/lib/utils/index.d.ts +2 -0
- package/lib/utils/index.js +1 -0
- package/lib/utils/jsx-survival.d.ts +31 -0
- package/lib/utils/jsx-survival.js +102 -0
- package/lib/utils/scan-named-imports.d.ts +47 -0
- package/lib/utils/scan-named-imports.js +239 -0
- package/package.json +17 -10
package/lib/app/browser.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { RolldownOptions, RolldownPluginOption } from 'rolldown';
|
|
2
2
|
import type { PluginOptions } from '../types/plugin-options.js';
|
|
3
3
|
export interface BrowserBuildConfig {
|
|
4
|
+
/** Transforms that must see the ORIGINAL source; composed before the caller's plugins. */
|
|
5
|
+
prePlugins: RolldownPluginOption[];
|
|
4
6
|
options: RolldownOptions;
|
|
5
7
|
plugins: RolldownPluginOption[];
|
|
6
8
|
}
|
package/lib/app/browser.js
CHANGED
|
@@ -86,11 +86,13 @@ export const setupForBrowser = async (input) => {
|
|
|
86
86
|
// the unresolved-workspace-import guard below so the guard reports the same
|
|
87
87
|
// promise the alias layer made.
|
|
88
88
|
const aliasEntries = flattenAliases(aliasMap);
|
|
89
|
+
// Reflection reads the ORIGINAL TypeScript, so it runs before any user
|
|
90
|
+
// transform can strip the annotations out from under it (see `GjsifyConfig`).
|
|
91
|
+
const prePlugins = [deepkitPlugin({ reflection: input.pluginOptions.reflection })];
|
|
89
92
|
const plugins = [
|
|
90
93
|
gjsImportsEmptyPlugin(),
|
|
91
94
|
aliasPlugin({ entries: aliasEntries }),
|
|
92
95
|
blueprintPlugin(),
|
|
93
|
-
deepkitPlugin({ reflection: input.pluginOptions.reflection }),
|
|
94
96
|
cssAsStringPlugin(),
|
|
95
97
|
// `order: 'post'` — see app/gjs.ts. The browser target's whole job is to
|
|
96
98
|
// replace Node builtins with their `@gjsify/*` browser entries; when one
|
|
@@ -103,7 +105,7 @@ export const setupForBrowser = async (input) => {
|
|
|
103
105
|
isExternal: (id) => external.includes(id),
|
|
104
106
|
}),
|
|
105
107
|
];
|
|
106
|
-
return { options, plugins };
|
|
108
|
+
return { options, prePlugins, plugins };
|
|
107
109
|
};
|
|
108
110
|
function flattenAliases(map) {
|
|
109
111
|
const out = {};
|
package/lib/app/gjs.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import type { PluginOptions } from '../types/plugin-options.js';
|
|
|
3
3
|
import type { GiSystemProbe } from '../plugins/gi-runtime-paths.js';
|
|
4
4
|
/** Resolved Rolldown configuration template + plugins for `--app gjs`. */
|
|
5
5
|
export interface GjsBuildConfig {
|
|
6
|
+
/** Transforms that must see the ORIGINAL source; composed before the caller's plugins. */
|
|
7
|
+
prePlugins: RolldownPluginOption[];
|
|
6
8
|
options: RolldownOptions;
|
|
7
9
|
plugins: RolldownPluginOption[];
|
|
8
10
|
}
|
package/lib/app/gjs.js
CHANGED
|
@@ -9,6 +9,9 @@ import { aliasPlugin } from '../plugins/alias.js';
|
|
|
9
9
|
import { externalsPlugin } from '../plugins/externals.js';
|
|
10
10
|
import { napiNodeAddonPlugin } from '../plugins/napi-node-addon.js';
|
|
11
11
|
import { unresolvedWorkspaceImportPlugin } from '../plugins/unresolved-workspace-import.js';
|
|
12
|
+
import { platformResolvePlugin, desktopSuffixChain, desktopOsSuffix, DESKTOP_REFUSED_SUFFIXES, } from '../plugins/platform-resolve.js';
|
|
13
|
+
import { reactNativeAliasPlugin } from '../plugins/react-native-alias.js';
|
|
14
|
+
import { reactNativeSupportGatePlugin } from '../plugins/react-native-gate.js';
|
|
12
15
|
import { deepkitPlugin } from '@gjsify/rolldown-plugin-deepkit';
|
|
13
16
|
import blueprintPlugin from '@gjsify/vite-plugin-blueprint';
|
|
14
17
|
import { getAliasesForGjs } from '../utils/alias.js';
|
|
@@ -158,6 +161,18 @@ export const setupForGjs = async (input) => {
|
|
|
158
161
|
'random-access-file': 'random-access-file/index.js',
|
|
159
162
|
...flattenAliases(aliasMap),
|
|
160
163
|
};
|
|
164
|
+
// Reflection reads the ORIGINAL TypeScript, so it runs before any user
|
|
165
|
+
// transform can strip the annotations out from under it (see `GjsifyConfig`).
|
|
166
|
+
const prePlugins = [deepkitPlugin({ reflection: input.pluginOptions.reflection })];
|
|
167
|
+
// ADR 0032 § 8's build-time gate reads the ORIGINAL source for the same reason
|
|
168
|
+
// reflection does: `import type { ViewProps }` is what tells it a name costs
|
|
169
|
+
// nothing, and a normal-order transform may already have stripped it.
|
|
170
|
+
// This target composes a dialect, so it is one of `DIALECT_APPS` — the set
|
|
171
|
+
// the CLI refuses `--dialect` against for every OTHER target. Adding a
|
|
172
|
+
// dialect here without adding the target there means a flag accepted and
|
|
173
|
+
// silently ignored.
|
|
174
|
+
if (input.pluginOptions.dialect === 'react-native')
|
|
175
|
+
prePlugins.push(reactNativeSupportGatePlugin());
|
|
161
176
|
const plugins = [
|
|
162
177
|
// Virtual-entry plugin runs FIRST so its resolveId/load match the
|
|
163
178
|
// synthetic input ids that `wrapInputWithSideEffects` produces.
|
|
@@ -167,6 +182,22 @@ export const setupForGjs = async (input) => {
|
|
|
167
182
|
// banner, and acorn (the auto-globals detector) rejects the `#` byte.
|
|
168
183
|
// The final-output shebang is composed by shebangPlugin's renderChunk.
|
|
169
184
|
inputShebangStripPlugin(),
|
|
185
|
+
// Platform-file forks for the desktop, ADR 0032 § 9: `.gtk` → `.<os>` →
|
|
186
|
+
// `.desktop` → base. BEFORE the alias layer, so a platform fork of a
|
|
187
|
+
// module that also has a Node-builtin substitution wins over the
|
|
188
|
+
// substitution — the fork is the more specific statement. `.native` and
|
|
189
|
+
// `.web` are deliberately absent from the chain and warned about when
|
|
190
|
+
// present; `DESKTOP_REFUSED_SUFFIXES` carries the reason.
|
|
191
|
+
platformResolvePlugin({
|
|
192
|
+
suffixes: desktopSuffixChain(desktopOsSuffix()),
|
|
193
|
+
refusedSuffixes: DESKTOP_REFUSED_SUFFIXES,
|
|
194
|
+
siblingIndex: true,
|
|
195
|
+
}),
|
|
196
|
+
// ADR 0032 § 2's alias line, only when the consumer asked for it
|
|
197
|
+
// (`--dialect react-native`). `pre`, ahead of the substitution table and the
|
|
198
|
+
// externals policy — a redirect after `externalsPlugin` would find the
|
|
199
|
+
// specifier already externalised.
|
|
200
|
+
...(input.pluginOptions.dialect === 'react-native' ? [reactNativeAliasPlugin()] : []),
|
|
170
201
|
// random-access-file's 'browser' field maps to a throwing stub; the alias
|
|
171
202
|
// table forces the fs-backed Node entry.
|
|
172
203
|
aliasPlugin({ entries: aliasEntries }),
|
|
@@ -184,7 +215,6 @@ export const setupForGjs = async (input) => {
|
|
|
184
215
|
// first.
|
|
185
216
|
externalsPlugin(external, { name: 'gjsify-gjs-externalize' }),
|
|
186
217
|
blueprintPlugin(),
|
|
187
|
-
deepkitPlugin({ reflection: input.pluginOptions.reflection }),
|
|
188
218
|
// GTK4's CSS engine is much older than browser engines — its
|
|
189
219
|
// parser predates nesting + many modern selectors. Targeting
|
|
190
220
|
// `firefox: 60 << 16` makes lightningcss flatten the source
|
|
@@ -218,7 +248,7 @@ export const setupForGjs = async (input) => {
|
|
|
218
248
|
: {}),
|
|
219
249
|
}),
|
|
220
250
|
];
|
|
221
|
-
return { options, plugins };
|
|
251
|
+
return { options, prePlugins, plugins };
|
|
222
252
|
};
|
|
223
253
|
/** Copy the alias map, dropping entries with an empty target. */
|
|
224
254
|
function flattenAliases(map) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { RolldownOptions, RolldownPluginOption } from 'rolldown';
|
|
2
2
|
import type { PluginOptions } from '../types/plugin-options.js';
|
|
3
3
|
export interface NativescriptBuildConfig {
|
|
4
|
+
/** Transforms that must see the ORIGINAL source; composed before the caller's plugins. */
|
|
5
|
+
prePlugins: RolldownPluginOption[];
|
|
4
6
|
options: RolldownOptions;
|
|
5
7
|
plugins: RolldownPluginOption[];
|
|
6
8
|
}
|
package/lib/app/nativescript.js
CHANGED
|
@@ -37,7 +37,7 @@ import { deepkitPlugin } from '@gjsify/rolldown-plugin-deepkit';
|
|
|
37
37
|
import { ALIASES_NODE_FOR_NATIVESCRIPT, getDerivedAliasesSync } from '@gjsify/resolve-npm';
|
|
38
38
|
import { globToEntryPoints } from '../utils/entry-points.js';
|
|
39
39
|
import { gjsImportsEmptyPlugin } from '../plugins/gjs-imports-empty.js';
|
|
40
|
-
import { platformResolvePlugin, detectNativescriptPlatform, nativescriptPlatformDefines, } from '../plugins/platform-resolve.js';
|
|
40
|
+
import { platformResolvePlugin, nativescriptSuffixChain, detectNativescriptPlatform, nativescriptPlatformDefines, } from '../plugins/platform-resolve.js';
|
|
41
41
|
import { unresolvedWorkspaceImportPlugin } from '../plugins/unresolved-workspace-import.js';
|
|
42
42
|
// Never bundled: an app works only with the ONE core instance the runtime boots, and what
|
|
43
43
|
// this target emits is an ENTRY the NS bundler resolves it for. An optional peer absent from
|
|
@@ -124,18 +124,20 @@ export const setupForNativescript = async (input) => {
|
|
|
124
124
|
// the unresolved-workspace-import guard below so the guard reports the same
|
|
125
125
|
// promise the alias layer made.
|
|
126
126
|
const aliasEntries = flattenAliases(aliasMap);
|
|
127
|
+
// Reflection reads the ORIGINAL TypeScript, so it runs before any user
|
|
128
|
+
// transform can strip the annotations out from under it (see `GjsifyConfig`).
|
|
129
|
+
const prePlugins = [deepkitPlugin({ reflection: input.pluginOptions.reflection })];
|
|
127
130
|
const plugins = [
|
|
128
131
|
gjsImportsEmptyPlugin(),
|
|
129
132
|
// Platform-specific source variants (`*.android` / `*.ios` /
|
|
130
133
|
// `*.native`) win over the base file — resolved BEFORE the Node-builtin
|
|
131
134
|
// alias routing so a platform fork of a portable module is honored.
|
|
132
|
-
platformResolvePlugin({ platform }),
|
|
135
|
+
platformResolvePlugin({ suffixes: nativescriptSuffixChain(platform) }),
|
|
133
136
|
aliasPlugin({ entries: aliasEntries }),
|
|
134
137
|
// NO blueprintPlugin — Blueprint is a GTK-specific UI DSL
|
|
135
138
|
// NO cssAsStringPlugin — NS ships its own CSS pipeline via
|
|
136
139
|
// @nativescript/core; .css imports are handled by the consuming
|
|
137
140
|
// @nativescript/webpack or @nativescript/vite build
|
|
138
|
-
deepkitPlugin({ reflection: input.pluginOptions.reflection }),
|
|
139
141
|
// `order: 'post'` — see app/gjs.ts. Native-bridge identifiers
|
|
140
142
|
// (`java.*`, `NS*`, …) are ambient GLOBALS on this target, never
|
|
141
143
|
// imports, so they never reach this hook; only a `@gjsify/*` edge does,
|
|
@@ -147,7 +149,7 @@ export const setupForNativescript = async (input) => {
|
|
|
147
149
|
isExternal,
|
|
148
150
|
}),
|
|
149
151
|
];
|
|
150
|
-
return { options, plugins };
|
|
152
|
+
return { options, prePlugins, plugins };
|
|
151
153
|
};
|
|
152
154
|
function flattenAliases(map) {
|
|
153
155
|
const out = {};
|
package/lib/app/node.d.ts
CHANGED
|
@@ -6,12 +6,15 @@ import type { PluginOptions } from '../types/plugin-options.js';
|
|
|
6
6
|
* factory, matchMedia, …) over `@gjsify/node-gi`, not the default `@gjsify/empty`
|
|
7
7
|
* stubs that keep CROSS-PLATFORM node bundles loadable on plain Node.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
9
|
+
* Three adjustments over the standard node alias map:
|
|
10
10
|
* 1. every `<pkg>/register…` → `@gjsify/empty` entry is DROPPED so the register
|
|
11
11
|
* import resolves to its real body;
|
|
12
12
|
* 2. the gjs target's bare→scoped register routes (`xmlhttprequest/register` →
|
|
13
13
|
* `@gjsify/fetch/register/xhr`, …) are merged in so the inject stub's bare
|
|
14
|
-
* specifiers resolve on node exactly as they do on gjs
|
|
14
|
+
* specifiers resolve on node exactly as they do on gjs;
|
|
15
|
+
* 3. `REVERSE_BRIDGE_ENTRY_ROUTES` is merged in, so a package whose `node`
|
|
16
|
+
* export condition is a different program (solid-js's SSR build) is routed
|
|
17
|
+
* to the entry the `--app gjs` build of the same source gets.
|
|
15
18
|
*
|
|
16
19
|
* Applied to the BASE map only, when `isGjsSourceBuild` — `pluginOptions.aliases` /
|
|
17
20
|
* user aliases still override, and a non-reverse-bridge build is untouched.
|
|
@@ -54,6 +57,8 @@ export declare function isGjsSourceBuild(options: {
|
|
|
54
57
|
registerInject?: string | undefined;
|
|
55
58
|
}): boolean;
|
|
56
59
|
export interface NodeBuildConfig {
|
|
60
|
+
/** Transforms that must see the ORIGINAL source; composed before the caller's plugins. */
|
|
61
|
+
prePlugins: RolldownPluginOption[];
|
|
57
62
|
options: RolldownOptions;
|
|
58
63
|
plugins: RolldownPluginOption[];
|
|
59
64
|
}
|
package/lib/app/node.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// `--app node` Rolldown configuration factory.
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
2
3
|
import { aliasPlugin } from '../plugins/alias.js';
|
|
3
4
|
import blueprintPlugin from '@gjsify/vite-plugin-blueprint';
|
|
4
5
|
import { deepkitPlugin } from '@gjsify/rolldown-plugin-deepkit';
|
|
@@ -10,6 +11,9 @@ import { cssAsStringPlugin } from '../plugins/css-as-string.js';
|
|
|
10
11
|
import { gjsImportsEmptyPlugin } from '../plugins/gjs-imports-empty.js';
|
|
11
12
|
import { gjsGiNodePlugin, gjsBuiltinModulesNodePlugin } from '../plugins/gjs-gi-node.js';
|
|
12
13
|
import { unresolvedWorkspaceImportPlugin } from '../plugins/unresolved-workspace-import.js';
|
|
14
|
+
import { platformResolvePlugin, desktopSuffixChain, desktopOsSuffix, DESKTOP_REFUSED_SUFFIXES, } from '../plugins/platform-resolve.js';
|
|
15
|
+
import { reactNativeAliasPlugin } from '../plugins/react-native-alias.js';
|
|
16
|
+
import { reactNativeSupportGatePlugin } from '../plugins/react-native-gate.js';
|
|
13
17
|
import { wrapInputWithSideEffects } from '../utils/entry-wrapper.js';
|
|
14
18
|
/**
|
|
15
19
|
* Side-effect specifier the node target injects when GJS ambient globals
|
|
@@ -38,18 +42,100 @@ const NODE_GI_GLOBALS_SPECIFIER = '@gjsify/node-gi/globals';
|
|
|
38
42
|
const NODE_GI_BARE_MODULE_SPECIFIERS = Object.values(ALIASES_GJS_FOR_NODE);
|
|
39
43
|
/** Matches a register subpath in an alias key: `<pkg>/register` or `<pkg>/register/<feature>`. */
|
|
40
44
|
const REGISTER_SUBPATH_RE = /\/register(\/|$)/;
|
|
45
|
+
/**
|
|
46
|
+
* npm packages a REVERSE-BRIDGE build must be routed away from, because their
|
|
47
|
+
* `node` export condition is not a node build of the same program — it is a
|
|
48
|
+
* DIFFERENT program, chosen for an assumption a GJS-on-node bundle does not
|
|
49
|
+
* hold. The value is the entry the `--app gjs` build of the same source gets,
|
|
50
|
+
* so the two ADR 0030 legs run one program.
|
|
51
|
+
*
|
|
52
|
+
* `solid-js` is the measured case and so far the only one. Its export map
|
|
53
|
+
* routes `node` (and `deno`, and `worker`) to `dist/server.js` — the SSR
|
|
54
|
+
* build, whose `createEffect` is `function createEffect(fn, value) {}`, an
|
|
55
|
+
* empty body. The initial render is therefore PERFECT and every reactive
|
|
56
|
+
* update silently never reaches GTK: sixteen of @gjsify/gtk-host's node-leg
|
|
57
|
+
* suites failed as `Expected ["second"], Actual ["first"]` while the identical
|
|
58
|
+
* source passed on gjs, and under ADR 0030's "same suite green on gjs ⇒
|
|
59
|
+
* node-gi defect" that read as a binding defect for as long as it stood. The
|
|
60
|
+
* map's own top-level `import`/`require` keys already point at the client
|
|
61
|
+
* build — `node` merely shadows them, being declared first.
|
|
62
|
+
*
|
|
63
|
+
* A ROUTE and not a condition change, deliberately. The obvious fix is to stop
|
|
64
|
+
* applying `node`, and it does not work: `platform: 'node'` implies that
|
|
65
|
+
* condition whatever `conditionNames` says (measured — dropping it from the
|
|
66
|
+
* list left solid-js on the SSR build). The reachable lever is `browser`,
|
|
67
|
+
* which outranks `node` in solid-js's map, and taking it wholesale is the
|
|
68
|
+
* SYMMETRIC defect: the gjs target can afford `browser` only because
|
|
69
|
+
* `getAliasesForGjs` has already replaced the node-facing npm packages, while
|
|
70
|
+
* the reverse bridge lifts the `/register` routes and keeps the rest real. Then
|
|
71
|
+
* `ws` — whose map declares `browser` FIRST, pointing at a one-line
|
|
72
|
+
* `throw new Error('ws does not work in the browser…')` — took the node-gi
|
|
73
|
+
* consumer harness from `pass 19/19` to `0/5 passed, 10 failed`, every one
|
|
74
|
+
* `W.WebSocket is not a constructor`. One named package resolves differently;
|
|
75
|
+
* every other package, and every cross-platform node bundle, is byte-unchanged.
|
|
76
|
+
*/
|
|
77
|
+
const REVERSE_BRIDGE_ENTRY_ROUTES = {
|
|
78
|
+
// The client build both legs share. Subpaths need no route: `solid-js/universal`
|
|
79
|
+
// (the renderer @gjsify/gtk-host binds to) declares no `node` condition at all,
|
|
80
|
+
// and reaches the root through this same specifier.
|
|
81
|
+
'solid-js': 'solid-js/dist/solid.js',
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* A dangling route above is SILENT, and silence here restores exactly the bug
|
|
85
|
+
* the route exists to prevent. MEASURED: point the solid-js route at a file
|
|
86
|
+
* that does not exist and the build exits 0 with an EMPTY log — `aliasPlugin`
|
|
87
|
+
* answers an unresolvable target with `null`, which a `pre`-order `resolveId`
|
|
88
|
+
* means as "let the default chain continue", and the default chain is the
|
|
89
|
+
* `node` condition, i.e. the SSR build with the empty `createEffect`. So a
|
|
90
|
+
* route that stops resolving fails the BUILD instead, naming the package.
|
|
91
|
+
*
|
|
92
|
+
* The CONTROL is the unrouted resolution of the same specifier — what the
|
|
93
|
+
* route replaces. If that does not resolve, the package is not in this
|
|
94
|
+
* project's graph at all and there is nothing to route (the routes are a
|
|
95
|
+
* table, not a dependency list). If it DOES resolve, the routed entry must
|
|
96
|
+
* too: a package that is installed and whose client entry has moved is the
|
|
97
|
+
* one case that must not pass quietly.
|
|
98
|
+
*
|
|
99
|
+
* `createRequire` is used only as an `exports`-map-aware RESOLVER, as in
|
|
100
|
+
* `app/gjs.ts`'s `resolveShim`; nothing is loaded through it.
|
|
101
|
+
*/
|
|
102
|
+
function assertReverseBridgeRoutes() {
|
|
103
|
+
const require_ = createRequire(import.meta.url);
|
|
104
|
+
const resolves = (specifier) => {
|
|
105
|
+
try {
|
|
106
|
+
require_.resolve(specifier);
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
for (const [pkg, target] of Object.entries(REVERSE_BRIDGE_ENTRY_ROUTES)) {
|
|
114
|
+
if (!resolves(pkg))
|
|
115
|
+
continue; // not in this graph — see the CONTROL note
|
|
116
|
+
if (resolves(target))
|
|
117
|
+
continue;
|
|
118
|
+
throw new Error(`@gjsify/rolldown-plugin-gjsify: the reverse-bridge entry route for "${pkg}" is dangling — ` +
|
|
119
|
+
`"${target}" does not resolve, while "${pkg}" does. Without the route this build silently ` +
|
|
120
|
+
`takes ${pkg}'s "node" export condition, which is why the route exists; update ` +
|
|
121
|
+
`REVERSE_BRIDGE_ENTRY_ROUTES in app/node.ts to the entry the --app gjs build resolves.`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
41
124
|
/**
|
|
42
125
|
* Reverse-bridge register routing: a node build of a GENUINE GJS SOURCE needs the
|
|
43
126
|
* REAL `@gjsify/*` register bodies (document, HTMLCanvasElement, the `'2d'` context
|
|
44
127
|
* factory, matchMedia, …) over `@gjsify/node-gi`, not the default `@gjsify/empty`
|
|
45
128
|
* stubs that keep CROSS-PLATFORM node bundles loadable on plain Node.
|
|
46
129
|
*
|
|
47
|
-
*
|
|
130
|
+
* Three adjustments over the standard node alias map:
|
|
48
131
|
* 1. every `<pkg>/register…` → `@gjsify/empty` entry is DROPPED so the register
|
|
49
132
|
* import resolves to its real body;
|
|
50
133
|
* 2. the gjs target's bare→scoped register routes (`xmlhttprequest/register` →
|
|
51
134
|
* `@gjsify/fetch/register/xhr`, …) are merged in so the inject stub's bare
|
|
52
|
-
* specifiers resolve on node exactly as they do on gjs
|
|
135
|
+
* specifiers resolve on node exactly as they do on gjs;
|
|
136
|
+
* 3. `REVERSE_BRIDGE_ENTRY_ROUTES` is merged in, so a package whose `node`
|
|
137
|
+
* export condition is a different program (solid-js's SSR build) is routed
|
|
138
|
+
* to the entry the `--app gjs` build of the same source gets.
|
|
53
139
|
*
|
|
54
140
|
* Applied to the BASE map only, when `isGjsSourceBuild` — `pluginOptions.aliases` /
|
|
55
141
|
* user aliases still override, and a non-reverse-bridge build is untouched.
|
|
@@ -65,6 +151,8 @@ export function enableGjsRegistersForNode(baseAliases) {
|
|
|
65
151
|
if (REGISTER_SUBPATH_RE.test(key))
|
|
66
152
|
out[key] = value;
|
|
67
153
|
}
|
|
154
|
+
// 3. the packages whose `node` export is a different PROGRAM — see the table.
|
|
155
|
+
Object.assign(out, REVERSE_BRIDGE_ENTRY_ROUTES);
|
|
68
156
|
return out;
|
|
69
157
|
}
|
|
70
158
|
/**
|
|
@@ -153,6 +241,9 @@ export const setupForNode = async (input) => {
|
|
|
153
241
|
// predicate's doc comment. The node-gi golden `test/canvas2d-bridge.test.mjs`
|
|
154
242
|
// pins the asymmetry it fixed.
|
|
155
243
|
const gjsSourceBuild = isGjsSourceBuild({ ...input.pluginOptions, registerInject });
|
|
244
|
+
// Only a reverse-bridge build carries the routes, so only it is held to them.
|
|
245
|
+
if (gjsSourceBuild)
|
|
246
|
+
assertReverseBridgeRoutes();
|
|
156
247
|
const baseAliases = getAliasesForNode({ external });
|
|
157
248
|
const aliasMap = {
|
|
158
249
|
...(gjsSourceBuild ? enableGjsRegistersForNode(baseAliases) : baseAliases),
|
|
@@ -206,6 +297,18 @@ export const setupForNode = async (input) => {
|
|
|
206
297
|
},
|
|
207
298
|
treeshake: true,
|
|
208
299
|
};
|
|
300
|
+
// Reflection reads the ORIGINAL TypeScript, so it runs before any user
|
|
301
|
+
// transform can strip the annotations out from under it (see `GjsifyConfig`).
|
|
302
|
+
const prePlugins = [deepkitPlugin({ reflection: input.pluginOptions.reflection })];
|
|
303
|
+
// ADR 0032 § 8's build-time gate reads the ORIGINAL source for the same reason
|
|
304
|
+
// reflection does: `import type { ViewProps }` is what tells it a name costs
|
|
305
|
+
// nothing, and a normal-order transform may already have stripped it.
|
|
306
|
+
// This target composes a dialect, so it is one of `DIALECT_APPS` — the set
|
|
307
|
+
// the CLI refuses `--dialect` against for every OTHER target. Adding a
|
|
308
|
+
// dialect here without adding the target there means a flag accepted and
|
|
309
|
+
// silently ignored.
|
|
310
|
+
if (input.pluginOptions.dialect === 'react-native')
|
|
311
|
+
prePlugins.push(reactNativeSupportGatePlugin());
|
|
209
312
|
const plugins = [
|
|
210
313
|
// Virtual-entry plugin runs FIRST so its resolveId/load match the synthetic
|
|
211
314
|
// `\0gjsify-entry:` ids `wrapInputWithSideEffects` produces (no-op when
|
|
@@ -233,12 +336,27 @@ export const setupForNode = async (input) => {
|
|
|
233
336
|
// `requireGi('Adw','1')` with no lowercased-pkg→namespace map needed.
|
|
234
337
|
// This plugin's own `gi://` branch is dead on the node target.
|
|
235
338
|
gjsImportsEmptyPlugin({ emptyGirs: !gjsSourceBuild }),
|
|
339
|
+
// Platform-file forks for the desktop, ADR 0032 § 9: `.gtk` → `.<os>` →
|
|
340
|
+
// `.desktop` → base. BEFORE the alias layer, so a platform fork of a
|
|
341
|
+
// module that also has a Node-builtin substitution wins over the
|
|
342
|
+
// substitution — the fork is the more specific statement. `.native` and
|
|
343
|
+
// `.web` are deliberately absent from the chain and warned about when
|
|
344
|
+
// present; `DESKTOP_REFUSED_SUFFIXES` carries the reason.
|
|
345
|
+
platformResolvePlugin({
|
|
346
|
+
suffixes: desktopSuffixChain(desktopOsSuffix()),
|
|
347
|
+
refusedSuffixes: DESKTOP_REFUSED_SUFFIXES,
|
|
348
|
+
siblingIndex: true,
|
|
349
|
+
}),
|
|
350
|
+
// ADR 0032 § 2's alias line, only when the consumer asked for it
|
|
351
|
+
// (`--dialect react-native`). `pre`, ahead of the substitution table and the
|
|
352
|
+
// externals policy — a redirect after `externalsPlugin` would find the
|
|
353
|
+
// specifier already externalised.
|
|
354
|
+
...(input.pluginOptions.dialect === 'react-native' ? [reactNativeAliasPlugin()] : []),
|
|
236
355
|
aliasPlugin({ entries: aliasEntries }),
|
|
237
356
|
// Blueprint (.blp → XML string): the reverse bridge runs REAL GTK on Node,
|
|
238
357
|
// so a GJS app entry with composite-template windows must build for
|
|
239
358
|
// `--app node` too. Claims only `.blp` — inert for plain-Node bundles.
|
|
240
359
|
blueprintPlugin(),
|
|
241
|
-
deepkitPlugin({ reflection: input.pluginOptions.reflection }),
|
|
242
360
|
// On a node-gi build (REAL GTK on Node) lower the CSS to GTK 4's subset just
|
|
243
361
|
// as `--app gjs` does, so nesting/`&` is FLATTENED before it reaches
|
|
244
362
|
// `Gtk.CssProvider.load_from_string` — GTK 4's parser otherwise rejects
|
|
@@ -254,7 +372,7 @@ export const setupForNode = async (input) => {
|
|
|
254
372
|
// specifier at runtime.
|
|
255
373
|
unresolvedWorkspaceImportPlugin({ target: 'node', aliases: aliasEntries, isExternal: external }),
|
|
256
374
|
];
|
|
257
|
-
return { options, plugins };
|
|
375
|
+
return { options, prePlugins, plugins };
|
|
258
376
|
};
|
|
259
377
|
function flattenAliases(map) {
|
|
260
378
|
const out = {};
|
package/lib/index.d.ts
CHANGED
|
@@ -22,8 +22,16 @@ export { unresolvedWorkspaceImportPlugin, classifyImport, isWorkspaceSpecifier,
|
|
|
22
22
|
export type { WorkspaceImportGuardOptions, WorkspaceImportGuardTarget, ImportVerdict, ClassifyImportInput, UnresolvedWorkspaceImportDetails, } from './plugins/unresolved-workspace-import.js';
|
|
23
23
|
export { napiNodeAddonPlugin, resolveAddonPath, nearestPackageRoot, classifySpecifier, directNodeShim, nodeGypBuildShim, bindingsShim, napiRsShim, ADDON_FILTER_RE, isNapiRsPackageJson, isNapiRsSibling, isGjsifyNativeBridge, detectNapiRsEntry, hostNapiRsTriple, AddonNotBuiltError, } from './plugins/napi-node-addon.js';
|
|
24
24
|
export type { NapiNodeAddonPluginOptions, AddonPackageJson } from './plugins/napi-node-addon.js';
|
|
25
|
-
export { platformResolvePlugin, detectNativescriptPlatform, nativescriptPlatformDefines, } from './plugins/platform-resolve.js';
|
|
25
|
+
export { platformResolvePlugin, nativescriptSuffixChain, desktopSuffixChain, desktopOsSuffix, DESKTOP_OS_SUFFIXES, DESKTOP_REFUSED_SUFFIXES, PlatformVariantExternalError, detectNativescriptPlatform, nativescriptPlatformDefines, } from './plugins/platform-resolve.js';
|
|
26
26
|
export type { PlatformResolvePluginOptions, NativescriptPlatform } from './plugins/platform-resolve.js';
|
|
27
|
+
export { rnRouteManifestPlugin, renderRouteManifest, walkRoutes, RouteManifestError, RN_ROUTES_MODULE_ID, MAX_ROUTE_DEPTH, } from './plugins/rn-route-manifest.js';
|
|
28
|
+
export type { RnRouteManifestOptions, FoundRoute } from './plugins/rn-route-manifest.js';
|
|
29
|
+
export { reactNativeAliasPlugin, classifyReactNativeSpecifier, REACT_NATIVE_ALIAS_TARGET, REACT_NATIVE_SPECIFIER, ReactNativeDeepImportError, ReactNativeAliasTargetMissingError, } from './plugins/react-native-alias.js';
|
|
30
|
+
export type { ReactNativeSpecifierKind } from './plugins/react-native-alias.js';
|
|
31
|
+
export { reactNativeSupportGatePlugin, loadSupportTable, findSupportViolations, formatSupportViolations, formatOpaqueReference, formatUnreadableModule, SUPPORT_TABLE_SUBPATH, WATCHED_SPECIFIERS, ReactNativeUnsupportedImportError, SupportTableUnreadableError, } from './plugins/react-native-gate.js';
|
|
32
|
+
export type { ReactNativeSupportGateOptions, SupportTableReader, SupportTableResolver, SupportViolation, } from './plugins/react-native-gate.js';
|
|
33
|
+
export { scanNamedImports, ImportScanParseError } from './utils/scan-named-imports.js';
|
|
34
|
+
export type { NamedImport, OpaqueReference, ScanResult } from './utils/scan-named-imports.js';
|
|
27
35
|
export * from './plugin.js';
|
|
28
36
|
import { gjsifyPlugin } from './plugin.js';
|
|
29
37
|
export { gjsifyPlugin };
|
package/lib/index.js
CHANGED
|
@@ -14,7 +14,11 @@ export { gjsImportsEmptyPlugin } from './plugins/gjs-imports-empty.js';
|
|
|
14
14
|
export { externalsPlugin } from './plugins/externals.js';
|
|
15
15
|
export { unresolvedWorkspaceImportPlugin, classifyImport, isWorkspaceSpecifier, formatUnresolvedWorkspaceImport, buildReverseAliasIndex, UnresolvedWorkspaceImportError, } from './plugins/unresolved-workspace-import.js';
|
|
16
16
|
export { napiNodeAddonPlugin, resolveAddonPath, nearestPackageRoot, classifySpecifier, directNodeShim, nodeGypBuildShim, bindingsShim, napiRsShim, ADDON_FILTER_RE, isNapiRsPackageJson, isNapiRsSibling, isGjsifyNativeBridge, detectNapiRsEntry, hostNapiRsTriple, AddonNotBuiltError, } from './plugins/napi-node-addon.js';
|
|
17
|
-
export { platformResolvePlugin, detectNativescriptPlatform, nativescriptPlatformDefines, } from './plugins/platform-resolve.js';
|
|
17
|
+
export { platformResolvePlugin, nativescriptSuffixChain, desktopSuffixChain, desktopOsSuffix, DESKTOP_OS_SUFFIXES, DESKTOP_REFUSED_SUFFIXES, PlatformVariantExternalError, detectNativescriptPlatform, nativescriptPlatformDefines, } from './plugins/platform-resolve.js';
|
|
18
|
+
export { rnRouteManifestPlugin, renderRouteManifest, walkRoutes, RouteManifestError, RN_ROUTES_MODULE_ID, MAX_ROUTE_DEPTH, } from './plugins/rn-route-manifest.js';
|
|
19
|
+
export { reactNativeAliasPlugin, classifyReactNativeSpecifier, REACT_NATIVE_ALIAS_TARGET, REACT_NATIVE_SPECIFIER, ReactNativeDeepImportError, ReactNativeAliasTargetMissingError, } from './plugins/react-native-alias.js';
|
|
20
|
+
export { reactNativeSupportGatePlugin, loadSupportTable, findSupportViolations, formatSupportViolations, formatOpaqueReference, formatUnreadableModule, SUPPORT_TABLE_SUBPATH, WATCHED_SPECIFIERS, ReactNativeUnsupportedImportError, SupportTableUnreadableError, } from './plugins/react-native-gate.js';
|
|
21
|
+
export { scanNamedImports, ImportScanParseError } from './utils/scan-named-imports.js';
|
|
18
22
|
export * from './plugin.js';
|
|
19
23
|
import { gjsifyPlugin } from './plugin.js';
|
|
20
24
|
export { gjsifyPlugin };
|
package/lib/library/lib.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { RolldownOptions, RolldownPluginOption } from 'rolldown';
|
|
2
2
|
import type { PluginOptions } from '../types/plugin-options.js';
|
|
3
3
|
export interface LibBuildConfig {
|
|
4
|
+
/** Transforms that must see the ORIGINAL source; composed before the caller's plugins. */
|
|
5
|
+
prePlugins: RolldownPluginOption[];
|
|
4
6
|
options: RolldownOptions;
|
|
5
7
|
plugins: RolldownPluginOption[];
|
|
6
8
|
}
|
package/lib/library/lib.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// library config: consumers downstream (downstream apps using `gjsify
|
|
10
10
|
// build --app gjs|node|browser`) re-bundle and apply their own target
|
|
11
11
|
// lowering. Library output stays maximally portable.
|
|
12
|
+
import blueprintPlugin from '@gjsify/vite-plugin-blueprint';
|
|
12
13
|
import { aliasPlugin } from '../plugins/alias.js';
|
|
13
14
|
import { cssAsStringPlugin } from '../plugins/css-as-string.js';
|
|
14
15
|
import { externalsPlugin } from '../plugins/externals.js';
|
|
@@ -90,8 +91,18 @@ export const setupLib = async (input) => {
|
|
|
90
91
|
// tiny JS module re-exporting the CSS source, which preserveModules
|
|
91
92
|
// emits 1:1.
|
|
92
93
|
cssAsStringPlugin(),
|
|
94
|
+
// Same shape as the CSS hook above, and needed for the same reason: without it a `.blp`
|
|
95
|
+
// import reaches rolldown's JS parser, and a Blueprint file's first line — `using Gtk 4.0;`
|
|
96
|
+
// — is VALID JavaScript (a `using` resource declaration missing its initializer), so the
|
|
97
|
+
// build dies on "Using declarations must have an initializer" and nothing points at
|
|
98
|
+
// Blueprint. Only the app factories installed it, which quietly made Blueprint an
|
|
99
|
+
// application-only feature: a shared widget in a library package could not have a
|
|
100
|
+
// template, so its captions had to be assigned from TypeScript, where they carry no
|
|
101
|
+
// `translatable` attribute and no catalogue can ever reach them.
|
|
102
|
+
blueprintPlugin(),
|
|
93
103
|
];
|
|
94
|
-
|
|
104
|
+
// Library mode wires no reflection at all; the empty slot says so out loud.
|
|
105
|
+
return { options, prePlugins: [], plugins };
|
|
95
106
|
};
|
|
96
107
|
/**
|
|
97
108
|
* Whether a specifier should stay external in library mode: a bare /
|
package/lib/plugin.d.ts
CHANGED
|
@@ -3,6 +3,21 @@ import type { PluginOptions } from './types/plugin-options.js';
|
|
|
3
3
|
import type { GiSystemProbe } from './plugins/gi-runtime-paths.js';
|
|
4
4
|
export interface GjsifyConfig {
|
|
5
5
|
options: RolldownOptions;
|
|
6
|
+
/**
|
|
7
|
+
* Transforms that must see the ORIGINAL module source — composed BEFORE the
|
|
8
|
+
* caller's own plugins, while `plugins` stays after them.
|
|
9
|
+
*
|
|
10
|
+
* The split exists because the two positions answer different questions. User
|
|
11
|
+
* plugins go first so their `resolveId`/`load` wins over gjsify's alias and
|
|
12
|
+
* externals chain. But a user `transform: { order: 'pre' }` that REWRITES the
|
|
13
|
+
* source then also beats gjsify's own pre-transforms, and Deepkit's type
|
|
14
|
+
* compiler is one of them: `@gjsify/rolldown-plugin-solid` runs
|
|
15
|
+
* `@babel/preset-typescript` over every `.tsx`, so Deepkit was handed a file
|
|
16
|
+
* with no type annotations left. MEASURED on one `.tsx` carrying both a
|
|
17
|
+
* `typeOf<T>()` and a JSX tag: `const reflected = typeOf()` in the artifact,
|
|
18
|
+
* exit 0, not one diagnostic, and `Error: No type given` thrown at runtime.
|
|
19
|
+
*/
|
|
20
|
+
prePlugins: RolldownPluginOption[];
|
|
6
21
|
plugins: RolldownPluginOption[];
|
|
7
22
|
}
|
|
8
23
|
export interface GjsifyPluginInput {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const GJS_INTL_SEGMENTER_STUB: string;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// One-line banner that supplies `Intl.Segmenter` on a GJS built without it.
|
|
2
|
+
//
|
|
3
|
+
// THE INCIDENT — measured, not hypothetical. `gjsify ship`'s first CI leg on a
|
|
4
|
+
// bare `ubuntu-latest` (the one that exists to hand a `.deb` to a real `dpkg`)
|
|
5
|
+
// installed the shipped package, ran the binary, and got:
|
|
6
|
+
//
|
|
7
|
+
// JS ERROR: TypeError: Intl.Segmenter is not a constructor
|
|
8
|
+
//
|
|
9
|
+
// Not an old runtime: that host had **gjs 1.88.1**, the same version as the
|
|
10
|
+
// Fedora container where everything passes. The difference is the ICU the two
|
|
11
|
+
// were built against, so a version number does not predict it — and the `.deb`
|
|
12
|
+
// this project ships declares `Depends: gjs >= 1.82`, which apt satisfies on
|
|
13
|
+
// that host. Install succeeds, program does not start: verbatim the failure
|
|
14
|
+
// class `depends.ts` was written to prevent, one layer below where it looks.
|
|
15
|
+
//
|
|
16
|
+
// WHY THE CLI NEEDS IT AT ALL, and why no application code is involved:
|
|
17
|
+
// `@gjsify/cli` → `yargs@18` → `string-width@7` → `get-east-asian-width`, which
|
|
18
|
+
// constructs a `Intl.Segmenter` AT MODULE SCOPE. It is the argument parser, so
|
|
19
|
+
// every command dies, `--help` included — reproduced locally by deleting
|
|
20
|
+
// `Intl.Segmenter` before importing the published bundle.
|
|
21
|
+
//
|
|
22
|
+
// WHAT THIS FALLBACK IS, AND IS NOT. It segments by CODE POINT, not by grapheme
|
|
23
|
+
// cluster, because a correct cluster algorithm is a Unicode table this banner
|
|
24
|
+
// cannot carry. Measured against native, via `string-width`:
|
|
25
|
+
//
|
|
26
|
+
// 'hello' 5→5 'あいう' 6→6 '🇩🇪' 2→2 'café' 4→4 (identical)
|
|
27
|
+
// '👨👩👧👦' 2→8 '👍🏽' 2→4 (over-counted)
|
|
28
|
+
//
|
|
29
|
+
// So ASCII, CJK, flags and combining accents are exact; ZWJ sequences and skin
|
|
30
|
+
// tone modifiers come out too wide. The cost is a misaligned column in terminal
|
|
31
|
+
// output containing those emoji. The cost of not having it is that nothing runs
|
|
32
|
+
// at all. It is installed ONLY when the constructor is missing, so on every GJS
|
|
33
|
+
// with a full ICU — every currently green CI leg — this line does nothing.
|
|
34
|
+
//
|
|
35
|
+
// Kept on a single line, like the sibling banners: the banner runs before any
|
|
36
|
+
// source-map-aware machinery, so a newline here shifts every bundle line by one.
|
|
37
|
+
export const GJS_INTL_SEGMENTER_STUB = 'if(typeof Intl.Segmenter!=="function")Intl.Segmenter=class Segmenter{' +
|
|
38
|
+
'constructor(){}' +
|
|
39
|
+
'resolvedOptions(){return{locale:"en",granularity:"grapheme"}}' +
|
|
40
|
+
'segment(input){const s=String(input);return{input:s,[Symbol.iterator]:function*(){' +
|
|
41
|
+
'let i=0;for(const c of s){yield{segment:c,index:i,input:s};i+=c.length}}}}' +
|
|
42
|
+
'};';
|